├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.js ├── App.scss ├── assets └── images │ ├── correct.png │ ├── loader.svg │ ├── shopping_cart.png │ ├── slider_img_1.jpg │ └── slider_img_2.jpg ├── components ├── CartMessage │ ├── CartMessage.js │ └── CartMessage.scss ├── CartModal │ ├── CartModal.js │ └── CartModal.scss ├── Footer │ ├── Footer.js │ └── Footer.scss ├── Header │ ├── Header.js │ └── Header.scss ├── Loader │ ├── Loader.js │ └── Loader.scss ├── Navbar │ ├── Navbar.js │ └── Navbar.scss ├── Product │ ├── Product.js │ └── Product.scss ├── ProductList │ ├── ProductList.js │ └── ProductList.scss ├── Sidebar │ ├── Sidebar.js │ └── Sidebar.scss └── Slider │ ├── HeaderSlider.js │ └── HeaderSlider.scss ├── index.js ├── pages ├── CartPage │ ├── CartPage.js │ └── CartPage.scss ├── CategoryProductPage │ ├── CategoryProductPage.js │ └── CategoryProductPage.scss ├── HomePage │ ├── HomePage.js │ └── HomePage.scss ├── ProductSinglePage │ ├── ProductSinglePage.js │ └── ProductSinglePage.scss ├── SearchPage │ ├── SearchPage.js │ └── SearchPage.scss └── index.js ├── store ├── cartSlice.js ├── categorySlice.js ├── productSlice.js ├── searchSlice.js ├── sidebarSlice.js └── store.js └── utils ├── apiURL.js ├── constants.js ├── helpers.js ├── images.js └── status.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.0", 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-redux": "^8.0.5", 13 | "react-router-dom": "^6.4.3", 14 | "react-scripts": "5.0.1", 15 | "react-slick": "^0.29.0", 16 | "sass": "^1.56.1", 17 | "slick-carousel": "^1.8.1", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabinmagar/snapup-commerce-site-react-js/6825029e2329d0fe964ebccdd944e709013061d1/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | SnapUp. 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabinmagar/snapup-commerce-site-react-js/6825029e2329d0fe964ebccdd944e709013061d1/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabinmagar/snapup-commerce-site-react-js/6825029e2329d0fe964ebccdd944e709013061d1/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.js: -------------------------------------------------------------------------------- 1 | import './App.scss'; 2 | // react router v6 3 | import {BrowserRouter, Routes, Route} from 'react-router-dom'; 4 | // pages 5 | import {Home, CategoryProduct, ProductSingle, Cart, Search} from "./pages/index"; 6 | // components 7 | import Header from "./components/Header/Header"; 8 | import Sidebar from "./components/Sidebar/Sidebar"; 9 | import Footer from "./components/Footer/Footer"; 10 | import store from "./store/store"; 11 | import {Provider} from "react-redux"; 12 | 13 | function App() { 14 | return ( 15 |
16 | 17 | 18 |
19 | 20 | 21 | 22 | {/* home page route */} 23 | } /> 24 | {/* single product route */} 25 | } /> 26 | {/* category wise product listing route */} 27 | } /> 28 | {/* cart */} 29 | } /> 30 | {/* searched products */} 31 | } /> 32 | 33 | 34 |
38 | ); 39 | } 40 | 41 | export default App; 42 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); 2 | 3 | // variables 4 | $clr-black: #000; 5 | $clr-white: #fff; 6 | $clr-orange: #F94E30; 7 | $clr-light-orange: #FF6433; 8 | $clr-light-gray: #F2F2F2; 9 | $clr-light: #F4F4F4; 10 | $clr-whitesmoke: #F5F5F5; 11 | $clr-dark: #212529; 12 | $clr-danger: #DC3545; 13 | $clr-gray: #929292; 14 | $font-family-manrope: 'Manrope', sans-serif; 15 | $font-family-poppins: 'Poppins', sans-serif; 16 | $transition-ease: all 300ms ease-in-out; 17 | 18 | // global styles & resets 19 | *, 20 | *::after, 21 | *::before{ 22 | margin: 0; 23 | padding: 0; 24 | box-sizing: border-box; 25 | scroll-behavior: smooth; 26 | } 27 | html{ 28 | font-size: 10px; 29 | } 30 | body{ 31 | font-size: 1.6rem; 32 | line-height: 1.6; 33 | font-family: $font-family-poppins; 34 | color: $clr-black; 35 | } 36 | ul{ 37 | list-style-type: none; 38 | } 39 | a{ 40 | text-decoration: none; 41 | color: unset; 42 | } 43 | h1, h2, h3, h4, h5, h6{ 44 | text-transform: capitalize; 45 | } 46 | img{ 47 | width: 100%; 48 | display: block; 49 | } 50 | .img-cover{ 51 | width: 100%; 52 | height: 100%; 53 | object-fit: cover; 54 | } 55 | button{ 56 | cursor: pointer; 57 | outline: 0; 58 | border: none; 59 | background-color: transparent; 60 | font-family: inherit; 61 | font-size: 1.8rem; 62 | } 63 | input, textarea, select{ 64 | outline: 0; 65 | border: none; 66 | resize: none; 67 | font-family: inherit; 68 | font-size: 1.6rem; 69 | } 70 | 71 | /* custom utility classes */ 72 | .container{ 73 | max-width: 1280px; 74 | margin: 0 auto; 75 | padding: 0 2rem; 76 | } 77 | 78 | /* flexbox and grid */ 79 | .flex{ 80 | display: flex; 81 | &-column{ flex-direction: column;} 82 | &-wrap{flex-wrap: wrap;} 83 | } 84 | .align{ 85 | &-center{align-items: center;} 86 | &-start{align-items: flex-start;} 87 | &-end{align-items: flex-end;} 88 | &-stretch{align-items: stretch;} 89 | } 90 | .justify{ 91 | &-center{justify-content: center;} 92 | &-between{ justify-content: space-between;} 93 | &-start{ justify-content: flex-start;} 94 | &-end{ justify-content: flex-end;} 95 | } 96 | .grid{ display: grid; } 97 | 98 | /* height and width */ 99 | .h-100{height: 100%;} 100 | .w-100{width: 100%;} 101 | 102 | /* text align and transformation */ 103 | .text{ 104 | &-center{text-align: center;} 105 | &-start{text-align: left;} 106 | &-end{text-align: right;} 107 | &-justify{text-align: justify;} 108 | &-uppercase{text-transform: uppercase;} 109 | &-capitalize{text-transform: capitalize;} 110 | &-orange{color: $clr-orange;} 111 | &-light-orange{color: $clr-light-orange;} 112 | &-light-gray{color: $clr-light-gray;} 113 | &-whitesmoke{color: $clr-whitesmoke;} 114 | &-white{color: $clr-white;} 115 | &-black{color: $clr-black;} 116 | &-dark{color: $clr-dark;} 117 | &-gray{color: $clr-gray;} 118 | &-danger{color: $clr-danger;} 119 | } 120 | 121 | .bg{ 122 | &-orange{background-color: $clr-orange;} 123 | &-light-orange{background-color: $clr-light-orange;} 124 | &-light-gray{background-color: $clr-light-gray;} 125 | &-light{background-color: $clr-light;} 126 | &-white{background-color: $clr-white;} 127 | &-black{background-color: $clr-black;} 128 | &-whitesmoke{background-color: $clr-whitesmoke;} 129 | &-dark{background-color: $clr-dark;} 130 | &-danger{background-color: $clr-danger;} 131 | &-gray{background-color: $clr-gray;} 132 | } 133 | 134 | .font{ 135 | &-poppins{font-family: $font-family-poppins;} 136 | &-manrope{font-family: $font-family-manrope;} 137 | } 138 | 139 | /* font weights */ 140 | .fw{ 141 | &-1{font-weight: 100;} 142 | &-2{font-weight: 200;} 143 | &-3{font-weight: 300;} 144 | &-4{font-weight: 400;} 145 | &-5{font-weight: 500;} 146 | &-6{font-weight: 600;} 147 | &-7{font-weight: 700;} 148 | &-8{font-weight: 800;} 149 | &-9{font-weight: 900;} 150 | } 151 | 152 | /* common font sizes */ 153 | .fs{ 154 | &-11{font-size: 11px;} 155 | &-12{font-size: 12px;} 156 | &-13{font-size: 13px;} 157 | &-14{font-size: 14px;} 158 | &-15{font-size: 15px;} 159 | &-16{font-size: 16px;} 160 | &-17{font-size: 17px;} 161 | &-18{font-size: 18px;} 162 | &-19{font-size: 19px;} 163 | &-20{font-size: 20px;} 164 | &-21{font-size: 21px;} 165 | &-22{font-size: 22px;} 166 | &-23{font-size: 23px;} 167 | &-24{font-size: 24px;} 168 | &-25{font-size: 25px;} 169 | &-26{font-size: 26px;} 170 | } 171 | 172 | /* letter spacing */ 173 | .ls-1{letter-spacing: 1px;} 174 | .ls-1h{letter-spacing: 0.5px;} 175 | .ls-2{letter-spacing: 2px;} 176 | .ls-2h{letter-spacing: 1.5px;} 177 | 178 | /* margin and padding */ 179 | .mx-auto{ 180 | margin-right: auto; 181 | margin-left: auto; 182 | } 183 | .py{ 184 | &-1{padding-top: 4px; padding-bottom: 4px;} 185 | &-2{padding-top: 8px; padding-bottom: 8px;} 186 | &-3{padding-top: 16px; padding-bottom: 16px;} 187 | &-4{padding-top: 24px; padding-bottom: 24px;} 188 | &-5{padding-top: 48px; padding-bottom: 48px;} 189 | &-6{padding-top: 60px; padding-bottom: 60px;} 190 | &-7{padding-top: 72px; padding-bottom: 72px;} 191 | } 192 | .my{ 193 | &-1{margin-top: 4px; margin-bottom: 4px;} 194 | &-2{margin-top: 8px; margin-bottom: 8px;} 195 | &-3{margin-top: 16px; margin-bottom: 16px;} 196 | &-4{margin-top: 24px; margin-bottom: 24px;} 197 | &-5{margin-top: 48px; margin-bottom: 48px;} 198 | &-6{margin-top: 60px; margin-bottom: 60px;} 199 | } 200 | 201 | .px{ 202 | &-1{padding-left: 4px; padding-right: 4px;} 203 | &-2{padding-left: 8px; padding-right: 8px;} 204 | &-3{padding-left: 16px; padding-right: 16px;} 205 | &-4{padding-left: 24px; padding-right: 24px;} 206 | &-5{padding-left: 48px; padding-right: 48px;} 207 | &-6{padding-left: 60px; padding-right: 60px;} 208 | &-7{padding-left: 72px; padding-right: 72px;} 209 | } 210 | .mx{ 211 | &-1{margin-left: 4px; margin-right: 4px;} 212 | &-2{margin-left: 8px; margin-right: 8px;} 213 | &-3{margin-left: 16px; margin-right: 16px;} 214 | &-4{margin-left: 24px; margin-right: 24px;} 215 | &-5{margin-left: 48px; margin-right: 48px;} 216 | &-6{margin-left: 60px; margin-right: 60px;} 217 | } 218 | 219 | // section 220 | .main-holder{ 221 | overflow-x: hidden; 222 | } 223 | 224 | // liness 225 | .vert-line{ 226 | width: 2px; 227 | background-color: lighten($clr-orange, 10%); 228 | height: 16px; 229 | margin: 0 12px; 230 | } 231 | .no-wrap{ 232 | white-space: nowrap; 233 | } 234 | 235 | // overflow 236 | .overflow-x-hidden{ 237 | overflow-x: hidden; 238 | } 239 | .overflow-y-hidden{ 240 | overflow-y: hidden; 241 | } 242 | 243 | // title 244 | .title-md{ 245 | padding: 1.2rem 2rem 1.2rem 4rem; 246 | border-bottom: 1px solid $clr-whitesmoke; 247 | background-color: $clr-white; 248 | position: relative; 249 | box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; 250 | 251 | h3{ 252 | color: rgba(0, 0, 0, 0.4); 253 | font-weight: 600; 254 | text-transform: uppercase; 255 | letter-spacing: 0.5px; 256 | font-size: 2.2rem; 257 | } 258 | 259 | &::before{ 260 | content: ""; 261 | position: absolute; 262 | left: 0; 263 | top: 0; 264 | width: 6px; 265 | height: 100%; 266 | background-color: $clr-orange; 267 | } 268 | } -------------------------------------------------------------------------------- /src/assets/images/correct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabinmagar/snapup-commerce-site-react-js/6825029e2329d0fe964ebccdd944e709013061d1/src/assets/images/correct.png -------------------------------------------------------------------------------- /src/assets/images/loader.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/images/shopping_cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabinmagar/snapup-commerce-site-react-js/6825029e2329d0fe964ebccdd944e709013061d1/src/assets/images/shopping_cart.png -------------------------------------------------------------------------------- /src/assets/images/slider_img_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabinmagar/snapup-commerce-site-react-js/6825029e2329d0fe964ebccdd944e709013061d1/src/assets/images/slider_img_1.jpg -------------------------------------------------------------------------------- /src/assets/images/slider_img_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabinmagar/snapup-commerce-site-react-js/6825029e2329d0fe964ebccdd944e709013061d1/src/assets/images/slider_img_2.jpg -------------------------------------------------------------------------------- /src/components/CartMessage/CartMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./CartMessage.scss"; 3 | import { correct } from "../../utils/images"; 4 | 5 | const CartMessage = () => { 6 | return ( 7 |
8 |
9 | 10 |
11 |
An item has been added to your shopping cart
12 |
13 | ) 14 | } 15 | 16 | export default CartMessage -------------------------------------------------------------------------------- /src/components/CartMessage/CartMessage.scss: -------------------------------------------------------------------------------- 1 | .cart-message{ 2 | padding: 2rem; 3 | position: fixed; 4 | top: 50%; 5 | left: 50%; 6 | transform: translate(-50%, -50%); 7 | background-color: rgba(0, 0, 0, 0.7); 8 | padding-top: 30px; 9 | padding-bottom: 30px; 10 | border-radius: 3px; 11 | 12 | .cart-message-icon{ 13 | margin-bottom: 1.7rem; 14 | img{ 15 | width: 45px; 16 | height: 45px; 17 | margin-right: auto; 18 | margin-left: auto; 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/components/CartModal/CartModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./CartModal.scss"; 3 | import { shopping_cart } from '../../utils/images'; 4 | import { formatPrice } from '../../utils/helpers'; 5 | 6 | const CartModal = ({carts}) => { 7 | return ( 8 |
9 |
Recenlty Added Products
10 | { 11 | (carts?.length > 0) ? ( 12 |
13 | { 14 | carts.map(cart => { 15 | return ( 16 |
17 |
18 | 19 |
20 |
{cart?.title}
21 |
22 | {formatPrice(cart?.discountedPrice)} 23 |
24 |
25 | ) 26 | }) 27 | } 28 | 29 |
view my shopping cart
30 |
) : ( 31 |
32 | 33 |
No products yet
34 |
35 | ) 36 | } 37 |
38 | ) 39 | } 40 | 41 | export default CartModal -------------------------------------------------------------------------------- /src/components/CartModal/CartModal.scss: -------------------------------------------------------------------------------- 1 | @import "../../App.scss"; 2 | 3 | .cart-modal{ 4 | position: absolute; 5 | right: -10px; 6 | top: calc(100% + 10px); 7 | background-color: $clr-white; 8 | width: 360px; 9 | box-shadow: rgba(100, 100, 111, 0.25) 0px 7px 29px 0px; 10 | padding: 1.8rem; 11 | border: 1px solid rgba(0, 0, 0, 0.1); 12 | visibility: hidden; 13 | opacity: 0; 14 | transition: $transition-ease; 15 | z-index: 99; 16 | height: 460px; 17 | overflow-y: scroll; 18 | 19 | &::-webkit-scrollbar{ 20 | width: 5px; 21 | 22 | } 23 | &::-webkit-scrollbar-track{ 24 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); 25 | border-radius: 10px; 26 | } 27 | &::-webkit-scrollbar-thumb{ 28 | border-radius: 10px; 29 | background-color: lighten($clr-light-orange, 5%); 30 | outline: 1px solid $clr-light-orange; 31 | } 32 | 33 | &::after{ 34 | position: absolute; 35 | content: ""; 36 | border-left: 10px solid transparent; 37 | border-right: 10px solid transparent; 38 | border-bottom: 13px solid $clr-white; 39 | right: 8px; 40 | top: -13px; 41 | } 42 | 43 | &-title{ 44 | color: rgba(0, 0, 0, 0.6); 45 | margin-bottom: 12px; 46 | } 47 | 48 | &-list{ 49 | .cart-modal-item{ 50 | grid-template-columns: 64px auto 65px; 51 | column-gap: 12px; 52 | border-bottom: 1px solid rgba(0, 0, 0, 0.05); 53 | &-img{ 54 | width: 60px; 55 | height: 60px; 56 | } 57 | &-title{ 58 | color: rgba(0, 0, 0, 0.85); 59 | } 60 | } 61 | } 62 | 63 | &-empty{ 64 | img{ 65 | width: 120px; 66 | margin-top: 2rem; 67 | } 68 | h6{ 69 | margin-top: 1.6rem; 70 | } 71 | } 72 | 73 | .view-cart-btn{ 74 | display: inline-block; 75 | width: 200px; 76 | padding: 5px 1rem; 77 | margin-top: 1.8rem; 78 | margin-left: auto; 79 | border-radius: 2px; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./Footer.scss"; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const Footer = () => { 6 | return ( 7 | 19 | ) 20 | } 21 | 22 | export default Footer -------------------------------------------------------------------------------- /src/components/Footer/Footer.scss: -------------------------------------------------------------------------------- 1 | .footer{ 2 | .copyright-text{ 3 | display: block; 4 | margin-top: 0.7rem; 5 | } 6 | } -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./Header.scss"; 3 | import {Link} from 'react-router-dom'; 4 | import Navbar from "../Navbar/Navbar"; 5 | 6 | const Header = () => { 7 | return ( 8 |
9 |
10 |
11 |
12 |
13 |
    14 |
  • 15 | {/* dummy links */} 16 | Seller Center 17 |
  • 18 |
  • 19 |
  • 20 | {/* dummy links */} 21 | Download 22 |
  • 23 |
  • 24 |
  • 25 | Follow us on 26 | 38 |
  • 39 |
40 |
41 |
42 |
    43 |
  • 44 | 45 | 46 | 47 | 48 | Support 49 | 50 |
  • 51 |
  • 52 |
  • 53 | 54 | Register 55 | 56 |
  • 57 |
  • 58 |
  • 59 | 60 | Log in 61 | 62 |
  • 63 |
64 |
65 |
66 | 67 |
68 | 69 |
70 |
71 |
72 |
73 | ) 74 | } 75 | 76 | export default Header -------------------------------------------------------------------------------- /src/components/Header/Header.scss: -------------------------------------------------------------------------------- 1 | @import "../../App.scss"; 2 | 3 | .header{ 4 | background: rgb(249,78,48); 5 | background: linear-gradient(180deg, rgba(249,78,48,1) 0%, rgba(255,100,51,1) 100%); 6 | 7 | .header-cnt-top{ 8 | border-bottom: 1px solid lighten($clr-orange, 10%); 9 | } 10 | 11 | @media screen and (max-width: 992px){ 12 | .header-cnt-top{ 13 | flex-direction: column; 14 | .header-cnt-top-l{ 15 | margin-bottom: 4px; 16 | } 17 | } 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/components/Loader/Loader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./Loader.scss"; 3 | import {loader} from "../../utils/images"; 4 | 5 | const Loader = () => { 6 | return ( 7 |
8 |
9 | 10 |
11 |
12 | ) 13 | } 14 | 15 | export default Loader 16 | -------------------------------------------------------------------------------- /src/components/Loader/Loader.scss: -------------------------------------------------------------------------------- 1 | .loader{ 2 | img{ 3 | width: 80px; 4 | } 5 | } -------------------------------------------------------------------------------- /src/components/Navbar/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import "./Navbar.scss"; 3 | import {Link} from "react-router-dom"; 4 | import { useSelector, useDispatch} from 'react-redux'; 5 | import { setSidebarOn } from '../../store/sidebarSlice'; 6 | import { getAllCategories } from '../../store/categorySlice'; 7 | import { getAllCarts, getCartItemsCount, getCartTotal } from '../../store/cartSlice'; 8 | import CartModal from "../CartModal/CartModal"; 9 | 10 | const Navbar = () => { 11 | const dispatch = useDispatch(); 12 | const categories = useSelector(getAllCategories); 13 | const carts = useSelector(getAllCarts); 14 | const itemsCount = useSelector(getCartItemsCount); 15 | const [searchTerm, setSearchTerm] = useState(""); 16 | 17 | const handleSearchTerm = (e) => { 18 | e.preventDefault(); 19 | setSearchTerm(e.target.value); 20 | } 21 | 22 | useEffect(() => { 23 | dispatch(getCartTotal()); 24 | }, [carts]) 25 | 26 | return ( 27 | 74 | ) 75 | } 76 | 77 | export default Navbar -------------------------------------------------------------------------------- /src/components/Navbar/Navbar.scss: -------------------------------------------------------------------------------- 1 | @import "../../App.scss"; 2 | 3 | .navbar{ 4 | padding-top: 12px; 5 | padding-bottom: 12px; 6 | .navbar-brand{ 7 | font-size: 2.4rem; 8 | } 9 | .brand-and-toggler{ 10 | .sidebar-show-btn{ 11 | margin-right: 12px; 12 | margin-top: 4px; 13 | transition: $transition-ease; 14 | &:hover{ 15 | opacity: 0.9; 16 | } 17 | } 18 | } 19 | .navbar-collapse{ 20 | .navbar-search{ 21 | padding: 4px; 22 | margin-left: 32px; 23 | border-radius: 2px; 24 | box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px; 25 | 26 | .form-control{ 27 | width: 100%; 28 | padding: 0 16px; 29 | color: rgba(0, 0, 0, 0.8); 30 | caret-color: $clr-orange; 31 | &::placeholder{ 32 | font-size: 13.5px; 33 | font-family: $font-family-manrope; 34 | letter-spacing: 0.5px; 35 | } 36 | } 37 | .search-btn{ 38 | background-color: $clr-orange; 39 | width: 60px; 40 | height: 32px; 41 | } 42 | 43 | @media screen and (max-width: 576px){ 44 | display: none; 45 | } 46 | } 47 | .navbar-nav{ 48 | margin-left: 32px; 49 | margin-top: 8px; 50 | .nav-item{ 51 | margin-right: 16px; 52 | .nav-link{ 53 | transition: $transition-ease; 54 | &:hover{ 55 | opacity: 0.95; 56 | } 57 | } 58 | } 59 | 60 | @media screen and (max-width: 992px){ 61 | display: none; 62 | } 63 | } 64 | } 65 | .navbar-cart{ 66 | margin-left: 32px; 67 | height: 30px; 68 | border-radius: 50%; 69 | font-size: 2rem; 70 | 71 | .cart-btn{ 72 | position: relative; 73 | .cart-items-value{ 74 | position: absolute; 75 | top: -10px; 76 | right: -8px; 77 | background-color: $clr-white; 78 | font-size: 14px; 79 | font-weight: 500; 80 | color: $clr-orange; 81 | padding: 0 0.5rem; 82 | width: 22px; 83 | border-radius: 50%; 84 | display: flex; 85 | align-items: center; 86 | justify-content: center; 87 | } 88 | 89 | &:hover{ 90 | .cart-modal{ 91 | opacity: 1; 92 | visibility: visible; 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/components/Product/Product.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import {formatPrice} from "../../utils/helpers"; 4 | import "./Product.scss"; 5 | 6 | const Product = ({product}) => { 7 | return ( 8 | 9 |
10 |
{product?.category}
11 |
12 | {product.title} 13 |
14 |
15 |
16 | Brand: 17 | {product?.brand} 18 |
19 |
20 | {product?.title} 21 |
22 |
23 | 24 | {formatPrice(product?.price)} 25 | 26 | 27 | {formatPrice(product?.discountedPrice)} 28 | 29 | 30 | ({product?.discountedPercentage}% Off) 31 | 32 |
33 |
34 |
35 | 36 | ) 37 | } 38 | 39 | export default Product -------------------------------------------------------------------------------- /src/components/Product/Product.scss: -------------------------------------------------------------------------------- 1 | @import "../../App.scss"; 2 | 3 | .product-item{ 4 | position: relative; 5 | border-radius: 8px; 6 | box-shadow: rgba(50, 50, 93, 0.05) 0px 2px 5px -1px, rgba(0, 0, 0, 0.05) 0px 1px 3px -1px; 7 | transition: $transition-ease; 8 | 9 | .category{ 10 | position: absolute; 11 | left: -5px; 12 | top: 1.6rem; 13 | background-color: $clr-orange; 14 | color: $clr-white; 15 | font-size: 13px; 16 | text-transform: capitalize; 17 | padding: 0.2rem 1rem; 18 | box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px, rgba(0, 0, 0, 0.24) 0px 1px 2px; 19 | 20 | &::after{ 21 | position: absolute; 22 | content: ""; 23 | left: 0; 24 | top: 100%; 25 | transform: translateY(-5px); 26 | width: 0; 27 | height: 0; 28 | border-top: 5px solid transparent; 29 | border-bottom: 5px solid transparent; 30 | border-right: 5px solid $clr-orange; 31 | } 32 | } 33 | 34 | &-img{ 35 | padding-bottom: 4px; 36 | margin-bottom: 12px; 37 | height: 250px; 38 | overflow: hidden!important; 39 | border-top-left-radius: 8px; 40 | border-top-right-radius: 8px; 41 | } 42 | 43 | &-info{ 44 | padding: 0 12px 20px 12px; 45 | text-align: center; 46 | font-family: $font-family-manrope; 47 | opacity: 0.8; 48 | 49 | .brand{ 50 | border-bottom: 1px solid rgba(0, 0, 0, 0.05); 51 | padding-bottom: 0.6rem; 52 | span{ 53 | &:first-child{ 54 | margin-right: 3px; 55 | } 56 | } 57 | } 58 | .title{ 59 | font-size: 14.5px; 60 | font-weight: 500; 61 | letter-spacing: 0.3px; 62 | } 63 | .price{ 64 | position: relative; 65 | .old-price{ 66 | opacity: 0.7; 67 | text-decoration: line-through; 68 | font-size: 12px; 69 | } 70 | .new-price{ 71 | margin: 0 1rem; 72 | font-weight: 700; 73 | font-size: 16px; 74 | } 75 | .discount{ 76 | font-size: 13px; 77 | font-weight: 600; 78 | font-family: $font-family-poppins; 79 | color: darken($clr-orange, 5%); 80 | } 81 | 82 | &::after{ 83 | content: ""; 84 | position: absolute; 85 | top: calc(100% + 6px); 86 | height: 1px; 87 | background-color: lighten($clr-orange, 10%); 88 | width: 60px; 89 | } 90 | } 91 | } 92 | 93 | &:hover{ 94 | box-shadow: rgba(50, 50, 93, 0.1) 0px 2px 5px -1px, rgba(0, 0, 0, 0.1) 0px 1px 20px -1px; 95 | } 96 | } -------------------------------------------------------------------------------- /src/components/ProductList/ProductList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./ProductList.scss"; 3 | import Product from "../Product/Product"; 4 | 5 | const ProductList = ({products}) => { 6 | return ( 7 |
8 | { 9 | products.map(product => { 10 | let discountedPrice = (product.price) - (product.price * (product.discountPercentage / 100)); 11 | 12 | return ( 13 | 14 | ) 15 | }) 16 | } 17 |
18 | ) 19 | } 20 | 21 | export default ProductList -------------------------------------------------------------------------------- /src/components/ProductList/ProductList.scss: -------------------------------------------------------------------------------- 1 | @import "../../App.scss"; 2 | .product-lists{ 3 | row-gap: 2rem; 4 | 5 | @media screen and (min-width: 576px){ 6 | grid-template-columns: repeat(2, 1fr); 7 | column-gap: 1.4rem; 8 | } 9 | 10 | @media screen and (min-width: 768px){ 11 | grid-template-columns: repeat(3, 1fr); 12 | } 13 | 14 | @media screen and (min-width: 1200px){ 15 | grid-template-columns: repeat(4, 1fr); 16 | } 17 | 18 | @media screen and (min-width: 1400px){ 19 | grid-template-columns: repeat(5, 1fr); 20 | } 21 | } -------------------------------------------------------------------------------- /src/components/Sidebar/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import "./Sidebar.scss"; 3 | import {Link} from 'react-router-dom'; 4 | import {useSelector, useDispatch} from 'react-redux'; 5 | import { getSidebarStatus, setSidebarOff } from '../../store/sidebarSlice'; 6 | import { fetchAsyncCategories, getAllCategories } from '../../store/categorySlice'; 7 | 8 | const Sidebar = () => { 9 | 10 | const dispatch = useDispatch(); 11 | const isSidebarOn = useSelector(getSidebarStatus); 12 | const categories = useSelector(getAllCategories); 13 | 14 | useEffect(() => { 15 | dispatch(fetchAsyncCategories()) 16 | }, [dispatch]) 17 | 18 | return ( 19 | 38 | ) 39 | } 40 | 41 | export default Sidebar -------------------------------------------------------------------------------- /src/components/Sidebar/Sidebar.scss: -------------------------------------------------------------------------------- 1 | @import "../../App.scss"; 2 | .sidebar{ 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | width: 300px; 7 | height: 100%; 8 | background-color: $clr-white; 9 | box-shadow: rgba(0, 0, 0, 0.1) 1.95px 1.95px 7px; 10 | padding: 2rem; 11 | transform: translateX(-100%); 12 | z-index: 1000; 13 | transition: $transition-ease; 14 | 15 | .cat-title{ 16 | padding-bottom: 1rem; 17 | } 18 | .sidebar-hide-btn{ 19 | position: absolute; 20 | right: 2rem; 21 | transition: $transition-ease; 22 | &:hover{ 23 | color: $clr-orange; 24 | } 25 | } 26 | .cat-list{ 27 | overflow-y: scroll; 28 | height: calc(100vh - 60px); 29 | li{ 30 | padding: 0.8rem 0; 31 | margin-right: 1.2rem; 32 | border-bottom: 0.5px solid rgba(0, 0, 0, 0.1); 33 | .cat-list-link{ 34 | font-size: 14px; 35 | font-family: $font-family-manrope; 36 | letter-spacing: 0.3px; 37 | transition: $transition-ease; 38 | 39 | &:hover{ 40 | color: $clr-orange; 41 | margin-left: 10px; 42 | } 43 | } 44 | } 45 | 46 | &::-webkit-scrollbar{ 47 | width: 5px; 48 | 49 | } 50 | &::-webkit-scrollbar-track{ 51 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); 52 | border-radius: 10px; 53 | } 54 | &::-webkit-scrollbar-thumb{ 55 | border-radius: 10px; 56 | background-color: lighten($clr-light-orange, 5%); 57 | outline: 1px solid $clr-light-orange; 58 | } 59 | } 60 | } 61 | 62 | .hide-sidebar{ 63 | transform: translateX(0); 64 | } -------------------------------------------------------------------------------- /src/components/Slider/HeaderSlider.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./HeaderSlider.scss"; 3 | import { sliderImgs } from "../../utils/images"; 4 | import Slider from 'react-slick'; 5 | import "slick-carousel/slick/slick.css"; 6 | import "slick-carousel/slick/slick-theme.css"; 7 | 8 | const HeaderSlider = () => { 9 | let settings = { 10 | autoplay: true, 11 | autoplaySpeed: 3000, 12 | arrows: false, 13 | dots: true, 14 | infinite: true, 15 | speed: 500, 16 | slidesToShow: 1, 17 | slidesToScroll: 1 18 | }; 19 | 20 | return ( 21 |
22 |
23 |
24 | 25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |
35 | ) 36 | } 37 | 38 | export default HeaderSlider -------------------------------------------------------------------------------- /src/components/Slider/HeaderSlider.scss: -------------------------------------------------------------------------------- 1 | .slider-item{ 2 | max-height: 300px; 3 | } 4 | .slider-content{ 5 | overflow-y: hidden; 6 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | 5 | const root = ReactDOM.createRoot(document.getElementById('root')); 6 | root.render( 7 | 8 | 9 | 10 | ); 11 | 12 | -------------------------------------------------------------------------------- /src/pages/CartPage/CartPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import "./CartPage.scss"; 3 | import { useSelector, useDispatch } from 'react-redux'; 4 | import { shopping_cart } from '../../utils/images'; 5 | import { Link } from 'react-router-dom'; 6 | import { formatPrice } from '../../utils/helpers'; 7 | import { getAllCarts, removeFromCart, toggleCartQty, clearCart, getCartTotal } from '../../store/cartSlice'; 8 | 9 | const CartPage = () => { 10 | const dispatch = useDispatch(); 11 | const carts = useSelector(getAllCarts); 12 | const { itemsCount, totalAmount} = useSelector((state) => state.cart); 13 | 14 | if(carts.length === 0){ 15 | return ( 16 |
17 |
18 | 19 | Your shopping cart is empty. 20 | Go shopping Now 21 |
22 |
23 | ) 24 | } 25 | 26 | return ( 27 |
28 |
29 |
30 |
31 |
32 |
33 | S.N. 34 |
35 |
36 | Product 37 |
38 |
39 | Unit Price 40 |
41 |
42 | Quantity 43 |
44 |
45 | Total Price 46 |
47 |
48 | Actions 49 |
50 |
51 |
52 | 53 |
54 | { 55 | carts.map((cart, idx) => { 56 | return ( 57 |
58 |
59 | {idx + 1} 60 |
61 |
62 | {cart?.title} 63 |
64 |
65 | {formatPrice(cart?.discountedPrice)} 66 |
67 |
68 |
69 | 72 | 73 |
74 | {cart?.quantity} 75 |
76 | 77 | 80 |
81 |
82 | 83 |
84 | {formatPrice(cart?.totalPrice)} 85 |
86 | 87 |
88 | 89 |
90 |
91 | ) 92 | }) 93 | } 94 |
95 | 96 |
97 |
98 | 102 |
103 | 104 |
105 |
106 |
Total ({itemsCount}) items:
107 | {formatPrice(totalAmount)} 108 |
109 | 110 | 111 |
112 |
113 |
114 |
115 |
116 | ) 117 | } 118 | 119 | export default CartPage -------------------------------------------------------------------------------- /src/pages/CartPage/CartPage.scss: -------------------------------------------------------------------------------- 1 | @import "../../App.scss"; 2 | 3 | .empty-cart{ 4 | min-height: 60vh; 5 | img{ 6 | width: 120px; 7 | margin-bottom: 1rem; 8 | } 9 | .shopping-btn{ 10 | padding: 0.65rem 2.8rem; 11 | margin-top: 1.6rem; 12 | border: 1px solid $clr-orange; 13 | transition: $transition-ease; 14 | 15 | &:hover{ 16 | background-color: transparent; 17 | color: $clr-orange; 18 | } 19 | } 20 | } 21 | 22 | .cart{ 23 | overflow-x: scroll; 24 | padding-top: 2rem; 25 | min-height: 85vh; 26 | 27 | &::-webkit-scrollbar{ 28 | height: 5px; 29 | 30 | } 31 | &::-webkit-scrollbar-track{ 32 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); 33 | border-radius: 10px; 34 | } 35 | &::-webkit-scrollbar-thumb{ 36 | border-radius: 10px; 37 | background-color: lighten($clr-light-orange, 5%); 38 | outline: 1px solid $clr-light-orange; 39 | } 40 | 41 | .container{ 42 | padding: 0; 43 | } 44 | .cart-ctable{ 45 | min-width: 1000px; 46 | .cart-ctr{ 47 | grid-template-columns: 1fr 4fr 2fr 2fr 2fr 2fr; 48 | min-height: 40px; 49 | border-bottom: 1px solid $clr-whitesmoke; 50 | display: grid; 51 | align-items: center!important; 52 | } 53 | .cart-chead{ 54 | margin-bottom: 2rem; 55 | padding-right: 2rem; 56 | padding-left: 2rem; 57 | box-shadow: rgba(60, 64, 67, 0.01) 0px 1px 2px 0px, rgba(60, 64, 67, 0.01) 0px 1px 3px 1px; 58 | border-radius: 3px; 59 | color: rgba(0, 0, 0, 0.6); 60 | } 61 | .cart-cbody{ 62 | padding-right: 2rem; 63 | padding-left: 2rem; 64 | box-shadow: rgba(60, 64, 67, 0.01) 0px 1px 2px 0px, rgba(60, 64, 67, 0.01) 0px 1px 3px 1px; 65 | border-radius: 3px; 66 | font-size: 14.5px; 67 | color: rgba(0, 0, 0, 0.6); 68 | } 69 | .delete-btn{ 70 | font-size: 14px; 71 | transition: $transition-ease; 72 | &:hover{ 73 | color: $clr-danger; 74 | } 75 | } 76 | .qty-change{ 77 | .qty-decrease, .qty-increase{ 78 | font-size: 13px; 79 | width: 28px; 80 | height: 28px; 81 | border: 1px solid rgba(0, 0, 0, 0.1); 82 | } 83 | .qty-value{ 84 | width: 36px; 85 | height: 28px; 86 | border-top: 1px solid rgba(0, 0, 0, 0.1); 87 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 88 | } 89 | } 90 | .cart-cfoot{ 91 | margin-top: 2rem; 92 | padding-right: 2rem; 93 | padding-left: 2rem; 94 | 95 | .total-txt{ 96 | margin-right: -1rem; 97 | } 98 | 99 | .clear-cart-btn{ 100 | border: 1px solid $clr-danger; 101 | padding: 0.7rem 1rem; 102 | transition: $transition-ease; 103 | 104 | &:hover{ 105 | background-color: $clr-danger; 106 | color: $clr-white; 107 | } 108 | } 109 | .checkout-btn{ 110 | padding: 0.8rem 2.8rem; 111 | margin-top: 0.7rem; 112 | } 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /src/pages/CategoryProductPage/CategoryProductPage.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import "./CategoryProductPage.scss"; 3 | import ProductList from "../../components/ProductList/ProductList"; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import { useParams } from 'react-router-dom'; 6 | import { getAllProductsByCategory, fetchAsyncProductsOfCategory, getCategoryProductsStatus } from '../../store/categorySlice'; 7 | import Loader from '../../components/Loader/Loader'; 8 | import { STATUS } from '../../utils/status'; 9 | 10 | const CategoryProductPage = () => { 11 | const dispatch = useDispatch(); 12 | const { category } = useParams(); 13 | const categoryProducts = useSelector(getAllProductsByCategory); 14 | const categoryProductsStatus = useSelector(getCategoryProductsStatus); 15 | 16 | useEffect(() => { 17 | dispatch(fetchAsyncProductsOfCategory(category)); 18 | }, [dispatch, category]); 19 | 20 | return ( 21 |
22 |
23 |
24 |
25 |

See our {category.replace("-", " ")}

26 |
27 | 28 | { 29 | categoryProductsStatus === STATUS.LOADING ? : 30 | } 31 |
32 |
33 |
34 | ) 35 | } 36 | 37 | export default CategoryProductPage -------------------------------------------------------------------------------- /src/pages/CategoryProductPage/CategoryProductPage.scss: -------------------------------------------------------------------------------- 1 | @import "../../App.scss"; 2 | 3 | .cat-products{ 4 | min-height: 100vh; 5 | } -------------------------------------------------------------------------------- /src/pages/HomePage/HomePage.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import "./HomePage.scss"; 3 | import HeaderSlider from "../../components/Slider/HeaderSlider"; 4 | import { useSelector, useDispatch } from 'react-redux'; 5 | import { getAllCategories } from '../../store/categorySlice'; 6 | import ProductList from "../../components/ProductList/ProductList"; 7 | import { fetchAsyncProducts, getAllProducts, getAllProductsStatus } from '../../store/productSlice'; 8 | import Loader from "../../components/Loader/Loader"; 9 | import { STATUS } from '../../utils/status'; 10 | 11 | const HomePage = () => { 12 | const dispatch = useDispatch(); 13 | const categories = useSelector(getAllCategories); 14 | 15 | useEffect(() => { 16 | dispatch(fetchAsyncProducts(50)); 17 | }, []); 18 | 19 | const products = useSelector(getAllProducts); 20 | const productStatus = useSelector(getAllProductsStatus); 21 | 22 | // randomizing the products in the list 23 | const tempProducts = []; 24 | if(products.length > 0){ 25 | for(let i in products){ 26 | let randomIndex = Math.floor(Math.random() * products.length); 27 | 28 | while(tempProducts.includes(products[randomIndex])){ 29 | randomIndex = Math.floor(Math.random() * products.length); 30 | } 31 | tempProducts[i] = products[randomIndex]; 32 | } 33 | } 34 | 35 | let catProductsOne = products.filter(product => product.category === categories[0]); 36 | let catProductsTwo = products.filter(product => product.category === categories[1]); 37 | let catProductsThree = products.filter(product => product.category === categories[2]); 38 | let catProductsFour = products.filter(product => product.category === categories[3]); 39 | 40 | return ( 41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 |
49 |
50 |

See our products

51 |
52 | { productStatus === STATUS.LOADING ? : } 53 |
54 | 55 |
56 |
57 |

{categories[0]}

58 |
59 | {productStatus === STATUS.LOADING ? : } 60 |
61 | 62 |
63 |
64 |

{categories[1]}

65 |
66 | {productStatus === STATUS.LOADING ? : } 67 |
68 | 69 |
70 |
71 |

{categories[2]}

72 |
73 | {productStatus === STATUS.LOADING ? : } 74 |
75 | 76 |
77 |
78 |

{categories[3]}

79 |
80 | {productStatus === STATUS.LOADING ? : } 81 |
82 |
83 |
84 |
85 |
86 | ) 87 | } 88 | 89 | export default HomePage -------------------------------------------------------------------------------- /src/pages/HomePage/HomePage.scss: -------------------------------------------------------------------------------- 1 | .slider-wrapper{ 2 | margin: 32px 0; 3 | } 4 | .main-content{ 5 | min-height: 100vh; 6 | } 7 | .categories{ 8 | .categories-item{ 9 | margin-bottom: 4.8rem!important; 10 | 11 | .title-md{ 12 | margin-bottom: 2.8rem; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/pages/ProductSinglePage/ProductSinglePage.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import "./ProductSinglePage.scss"; 3 | import {useParams} from "react-router-dom"; 4 | import {useSelector, useDispatch} from "react-redux"; 5 | import { fetchAsyncProductSingle, getProductSingle, getSingleProductStatus } from '../../store/productSlice'; 6 | import { STATUS } from '../../utils/status'; 7 | import Loader from "../../components/Loader/Loader"; 8 | import {formatPrice} from "../../utils/helpers"; 9 | import { addToCart, getCartMessageStatus, setCartMessageOff, setCartMessageOn } from '../../store/cartSlice'; 10 | import CartMessage from "../../components/CartMessage/CartMessage"; 11 | 12 | const ProductSinglePage = () => { 13 | const {id} = useParams(); 14 | const dispatch = useDispatch(); 15 | const product = useSelector(getProductSingle); 16 | const productSingleStatus = useSelector(getSingleProductStatus); 17 | const [quantity, setQuantity] = useState(1); 18 | const cartMessageStatus = useSelector(getCartMessageStatus); 19 | 20 | // getting single product 21 | useEffect(() => { 22 | dispatch(fetchAsyncProductSingle(id)); 23 | 24 | if(cartMessageStatus){ 25 | setTimeout(() => { 26 | dispatch(setCartMessageOff()); 27 | }, 2000); 28 | } 29 | }, [cartMessageStatus]); 30 | 31 | let discountedPrice = (product?.price) - (product?.price * (product?.discountPercentage / 100)); 32 | if(productSingleStatus === STATUS.LOADING) { 33 | return 34 | } 35 | 36 | const increaseQty = () => { 37 | setQuantity((prevQty) => { 38 | let tempQty = prevQty + 1; 39 | if(tempQty > product?.stock) tempQty = product?.stock; 40 | return tempQty; 41 | }) 42 | } 43 | 44 | const decreaseQty = () => { 45 | setQuantity((prevQty) => { 46 | let tempQty = prevQty - 1; 47 | if(tempQty < 1) tempQty = 1; 48 | return tempQty; 49 | }) 50 | } 51 | 52 | const addToCartHandler = (product) => { 53 | let discountedPrice = (product?.price) - (product?.price * (product?.discountPercentage / 100)); 54 | let totalPrice = quantity * discountedPrice; 55 | 56 | dispatch(addToCart({...product, quantity: quantity, totalPrice, discountedPrice})); 57 | dispatch(setCartMessageOn(true)); 58 | } 59 | 60 | 61 | return ( 62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 | 72 |
73 |
74 | 77 |
78 |
79 | 82 |
83 |
84 | 87 |
88 |
89 | 92 |
93 |
94 |
95 |
96 | 97 |
98 |
99 |
{product?.title}
100 |
101 |

{product?.description}

102 |
103 |
104 |
105 | Rating: 106 | 107 | {product?.rating} 108 | 109 |
110 |
111 |
112 | Brand: 113 | {product?.brand} 114 |
115 |
116 |
117 | Category: 118 | 119 | {product?.category ? product.category.replace("-", " ") : ""} 120 | 121 |
122 |
123 | 124 |
125 |
126 |
127 | {formatPrice(product?.price)} 128 |
129 | 130 | Inclusive of all taxes 131 | 132 |
133 | 134 |
135 |
136 | {formatPrice(discountedPrice)} 137 |
138 |
139 | {product?.discountPercentage}% OFF 140 |
141 |
142 |
143 | 144 |
145 |
Quantity:
146 |
147 | 150 |
{quantity}
151 | 154 |
155 | { 156 | (product?.stock === 0) ?
out of stock
: "" 157 | } 158 |
159 | 160 |
161 | 165 | 168 |
169 |
170 |
171 |
172 |
173 |
174 | 175 | {cartMessageStatus && } 176 |
177 | ) 178 | } 179 | 180 | export default ProductSinglePage -------------------------------------------------------------------------------- /src/pages/ProductSinglePage/ProductSinglePage.scss: -------------------------------------------------------------------------------- 1 | @import "../../App.scss"; 2 | 3 | .product-single{ 4 | .product-single-content{ 5 | row-gap: 2rem; 6 | padding: 12px; 7 | 8 | @media screen and (min-width: 768px){ 9 | grid-template-columns: repeat(2, 1fr); 10 | column-gap: 4rem; 11 | } 12 | 13 | .product-single-l{ 14 | .product-img{ 15 | .product-img-zoom{ 16 | height: 380px; 17 | overflow: hidden; 18 | } 19 | .product-img-thumbs{ 20 | overflow-x: scroll; 21 | 22 | &::-webkit-scrollbar{ 23 | height: 5px; 24 | } 25 | &::-webkit-scrollbar-track{ 26 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); 27 | border-radius: 10px; 28 | } 29 | &::-webkit-scrollbar-thumb{ 30 | border-radius: 10px; 31 | background-color: lighten($clr-light-orange, 5%); 32 | outline: 1px solid $clr-light-orange; 33 | } 34 | 35 | .thumb-item{ 36 | margin: 0 5px; 37 | height: 120px; 38 | border: 2px solid $clr-white; 39 | transition: $transition-ease; 40 | img{ 41 | transition: $transition-ease; 42 | } 43 | 44 | &:first-child{ 45 | margin-left: 0; 46 | } 47 | 48 | &:last-child{ 49 | margin-right: 0; 50 | } 51 | 52 | &:hover{ 53 | border-color: $clr-orange; 54 | img{ 55 | transform: scale(0.9); 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | .product-single-r{ 64 | margin-top: 2rem; 65 | .product-details{ 66 | .title{ 67 | padding-bottom: 0.6rem; 68 | border-bottom: 1px solid $clr-light-gray; 69 | } 70 | .para{ 71 | margin: 1rem 0; 72 | opacity: 0.9; 73 | } 74 | .info{ 75 | margin-bottom: 1.6rem; 76 | } 77 | .price{ 78 | background-color: rgba(0, 0, 0, 0.02); 79 | padding: 2rem; 80 | .old-price{ 81 | text-decoration: line-through; 82 | } 83 | .discount{ 84 | padding: 0 0.8rem; 85 | border-radius: 3px; 86 | margin-left: 8px; 87 | margin-top: 2px; 88 | } 89 | } 90 | .qty{ 91 | .qty-change{ 92 | .qty-decrease, .qty-increase{ 93 | width: 28px; 94 | height: 28px; 95 | border: 1px solid rgba(0, 0, 0, 0.1); 96 | font-size: 13px; 97 | } 98 | .qty-value{ 99 | width: 45px; 100 | height: 28px; 101 | border-top: 1px solid rgba(0, 0, 0, 0.1); 102 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 103 | } 104 | } 105 | .qty-error{ 106 | padding: 2px 6px; 107 | border-radius: 3px; 108 | } 109 | 110 | } 111 | .btns{ 112 | .btn{ 113 | height: 46px; 114 | font-size: 15px; 115 | border: 1px solid $clr-orange; 116 | padding: 0 1.8rem; 117 | transition: $transition-ease; 118 | 119 | &-text{ 120 | text-transform: capitalize; 121 | } 122 | &:first-child{ 123 | background-color: #FFEEE8; 124 | color: $clr-orange; 125 | } 126 | &:last-child{ 127 | background-color: $clr-orange; 128 | color: white; 129 | } 130 | &:hover{ 131 | opacity: 0.9; 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | @media screen and (min-width: 992px){ 139 | 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /src/pages/SearchPage/SearchPage.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import "./SearchPage.scss"; 3 | import { useSelector, useDispatch } from 'react-redux'; 4 | import { useParams } from 'react-router-dom'; 5 | import { STATUS } from '../../utils/status'; 6 | import Loader from '../../components/Loader/Loader'; 7 | import ProductList from '../../components/ProductList/ProductList'; 8 | import { fetchAsyncSearchProduct, getSearchProducts, setSearchTerm, getSearchProductsStatus, clearSearch } from '../../store/searchSlice'; 9 | 10 | const SearchPage = () => { 11 | const dispatch = useDispatch(); 12 | const {searchTerm } = useParams(); 13 | const searchProducts = useSelector(getSearchProducts); 14 | const searchProductsStatus = useSelector(getSearchProductsStatus); 15 | 16 | useEffect(() => { 17 | dispatch(clearSearch()); 18 | dispatch(fetchAsyncSearchProduct(searchTerm)); 19 | }, [searchTerm]); 20 | 21 | if(searchProducts.length === 0){ 22 | return ( 23 |
26 |
27 |

No Products found.

28 |
29 |
30 | ) 31 | } 32 | 33 | return ( 34 |
35 |
36 |
37 |
38 |
39 |

Search results:

40 |
41 |
42 | { 43 | searchProductsStatus === STATUS.LOADING ? : 44 | } 45 |
46 |
47 |
48 |
49 | ) 50 | } 51 | 52 | export default SearchPage; -------------------------------------------------------------------------------- /src/pages/SearchPage/SearchPage.scss: -------------------------------------------------------------------------------- 1 | .search-content{ 2 | border: 2px solid red; 3 | } -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import Home from "../pages/HomePage/HomePage"; 2 | import Cart from "../pages/CartPage/CartPage"; 3 | import CategoryProduct from "../pages/CategoryProductPage/CategoryProductPage"; 4 | import ProductSingle from "./ProductSinglePage/ProductSinglePage"; 5 | import Search from "./SearchPage/SearchPage"; 6 | 7 | export {Home, CategoryProduct, ProductSingle, Cart, Search}; -------------------------------------------------------------------------------- /src/store/cartSlice.js: -------------------------------------------------------------------------------- 1 | import {createSlice} from "@reduxjs/toolkit"; 2 | 3 | const fetchFromLocalStorage = () => { 4 | let cart = localStorage.getItem('cart'); 5 | if(cart){ 6 | return JSON.parse(localStorage.getItem('cart')); 7 | } else { 8 | return []; 9 | } 10 | } 11 | 12 | const storeInLocalStorage = (data) => { 13 | localStorage.setItem('cart', JSON.stringify(data)); 14 | } 15 | 16 | const initialState = { 17 | carts: fetchFromLocalStorage(), 18 | itemsCount: 0, 19 | totalAmount: 0, 20 | isCartMessageOn: false 21 | } 22 | 23 | const cartSlice = createSlice({ 24 | name: 'cart', 25 | initialState, 26 | reducers: { 27 | addToCart: (state, action) => { 28 | const isItemInCart = state.carts.find(item => item.id === action.payload.id); 29 | 30 | if(isItemInCart){ 31 | const tempCart = state.carts.map(item => { 32 | if(item.id === action.payload.id){ 33 | let tempQty = item.quantity + action.payload.quantity; 34 | let tempTotalPrice = tempQty * item.price; 35 | 36 | return { 37 | ...item, quantity: tempQty, totalPrice: tempTotalPrice 38 | } 39 | } else { 40 | return item; 41 | } 42 | }); 43 | 44 | state.carts = tempCart; 45 | storeInLocalStorage(state.carts); 46 | } else { 47 | state.carts.push(action.payload); 48 | storeInLocalStorage(state.carts); 49 | } 50 | }, 51 | 52 | removeFromCart: (state, action) => { 53 | const tempCart = state.carts.filter(item => item.id !== action.payload); 54 | state.carts = tempCart; 55 | storeInLocalStorage(state.carts); 56 | }, 57 | 58 | clearCart: (state) => { 59 | state.carts = []; 60 | storeInLocalStorage(state.carts); 61 | }, 62 | 63 | getCartTotal: (state) => { 64 | state.totalAmount = state.carts.reduce((cartTotal, cartItem) => { 65 | return cartTotal += cartItem.totalPrice 66 | }, 0); 67 | 68 | state.itemsCount = state.carts.length; 69 | }, 70 | 71 | toggleCartQty: (state, action) => { 72 | const tempCart = state.carts.map(item => { 73 | if(item.id === action.payload.id){ 74 | let tempQty = item.quantity; 75 | let tempTotalPrice = item.totalPrice; 76 | 77 | if(action.payload.type === "INC"){ 78 | tempQty++; 79 | if(tempQty === item.stock) tempQty = item.stock; 80 | tempTotalPrice = tempQty * item.discountedPrice; 81 | } 82 | 83 | if(action.payload.type === "DEC"){ 84 | tempQty--; 85 | if(tempQty < 1) tempQty = 1; 86 | tempTotalPrice = tempQty * item.discountedPrice; 87 | } 88 | 89 | return {...item, quantity: tempQty, totalPrice: tempTotalPrice}; 90 | } else { 91 | return item; 92 | } 93 | }); 94 | 95 | state.carts = tempCart; 96 | storeInLocalStorage(state.carts); 97 | }, 98 | 99 | setCartMessageOn: (state) => { 100 | state.isCartMessageOn = true; 101 | }, 102 | 103 | setCartMessageOff: (state) => { 104 | state.isCartMessageOn = false; 105 | } 106 | } 107 | }); 108 | 109 | export const {addToCart, setCartMessageOff, setCartMessageOn, getCartTotal, toggleCartQty, clearCart, removeFromCart} = cartSlice.actions; 110 | export const getAllCarts = (state) => state.cart.carts; 111 | export const getCartItemsCount = (state) => state.cart.itemsCount; 112 | export const getCartMessageStatus = (state) => state.cart.isCartMessageOn; 113 | 114 | export default cartSlice.reducer; -------------------------------------------------------------------------------- /src/store/categorySlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice} from "@reduxjs/toolkit"; 2 | import {BASE_URL} from "../utils/apiURL"; 3 | import {STATUS} from "../utils/status"; 4 | 5 | const initialState = { 6 | categories: [], 7 | categoriesStatus: STATUS.IDLE, 8 | categoryProducts: [], 9 | categoryProductsStatus: STATUS.IDLE 10 | } 11 | 12 | const categorySlice = createSlice({ 13 | name: 'category', 14 | initialState, 15 | reducers: {}, 16 | extraReducers: (builder) => { 17 | builder 18 | .addCase(fetchAsyncCategories.pending, (state, action) => { 19 | state.categoriesStatus = STATUS.LOADING; 20 | }) 21 | 22 | .addCase(fetchAsyncCategories.fulfilled, (state, action) => { 23 | state.categories = action.payload; 24 | state.categoriesStatus = STATUS.SUCCEEDED; 25 | }) 26 | 27 | .addCase(fetchAsyncCategories.rejected, (state, action) => { 28 | state.categoriesStatus = STATUS.FAILED; 29 | }) 30 | 31 | .addCase(fetchAsyncProductsOfCategory.pending, (state, action) => { 32 | state.categoryProductsStatus = STATUS.LOADING; 33 | }) 34 | 35 | .addCase(fetchAsyncProductsOfCategory.fulfilled, (state, action) => { 36 | state.categoryProducts = action.payload; 37 | state.categoryProductsStatus = STATUS.SUCCEEDED; 38 | }) 39 | 40 | .addCase(fetchAsyncProductsOfCategory.rejected, (state, action) => { 41 | state.categoryProductsStatus = STATUS.FAILED; 42 | }) 43 | } 44 | }); 45 | 46 | export const fetchAsyncCategories = createAsyncThunk('categories/fetch', async() => { 47 | const response = await fetch(`${BASE_URL}products/categories`); 48 | const data = await response.json(); 49 | return data; 50 | }); 51 | 52 | export const fetchAsyncProductsOfCategory = createAsyncThunk('category-products/fetch', async(category) => { 53 | const response = await fetch(`${BASE_URL}products/category/${category}`); 54 | const data = await response.json(); 55 | return data.products; 56 | }); 57 | 58 | export const getAllCategories = (state) => state.category.categories; 59 | export const getAllProductsByCategory = (state) => state.category.categoryProducts; 60 | export const getCategoryProductsStatus = (state) => state.category.categoryProductsStatus; 61 | export default categorySlice.reducer; -------------------------------------------------------------------------------- /src/store/productSlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; 2 | import { BASE_URL } from "../utils/apiURL"; 3 | import { STATUS } from "../utils/status"; 4 | 5 | const initialState = { 6 | products: [], 7 | productsStatus: STATUS.IDLE, 8 | productSingle: [], 9 | productSingleStatus: STATUS.IDLE 10 | } 11 | 12 | const productSlice = createSlice({ 13 | name: "product", 14 | initialState, 15 | reducers: {}, 16 | extraReducers: (builder) => { 17 | builder 18 | .addCase(fetchAsyncProducts.pending, (state, action) => { 19 | state.productsStatus = STATUS.LOADING; 20 | }) 21 | 22 | .addCase(fetchAsyncProducts.fulfilled, (state, action) => { 23 | state.products = action.payload; 24 | state.productsStatus = STATUS.SUCCEEDED; 25 | }) 26 | 27 | .addCase(fetchAsyncProducts.rejected, (state, action) => { 28 | state.productsStatus = STATUS.FAILED 29 | }) 30 | 31 | .addCase(fetchAsyncProductSingle.pending, (state, action) => { 32 | state.productSingleStatus = STATUS.LOADING; 33 | }) 34 | 35 | .addCase(fetchAsyncProductSingle.fulfilled, (state, action) => { 36 | state.productSingle = action.payload; 37 | state.productSingleStatus = STATUS.SUCCEEDED; 38 | }) 39 | 40 | .addCase(fetchAsyncProductSingle.rejected, (state, action) => { 41 | state.productSingleStatus = STATUS.FAILED; 42 | }) 43 | } 44 | }); 45 | 46 | // for getting the products list with limited numbers 47 | export const fetchAsyncProducts = createAsyncThunk('products/fetch', async(limit) => { 48 | const response = await fetch(`${BASE_URL}products?limit=${limit}`); 49 | const data = await response.json(); 50 | return data.products; 51 | }); 52 | 53 | // getting the single product data also 54 | export const fetchAsyncProductSingle = createAsyncThunk('product-single/fetch', async(id) => { 55 | const response = await fetch(`${BASE_URL}products/${id}`); 56 | const data = await response.json(); 57 | return data; 58 | }); 59 | 60 | 61 | export const getAllProducts = (state) => state.product.products; 62 | export const getAllProductsStatus = (state) => state.product.productsStatus; 63 | export const getProductSingle = (state) => state.product.productSingle; 64 | export const getSingleProductStatus = (state) => state.product.productSingleStatus; 65 | export default productSlice.reducer; -------------------------------------------------------------------------------- /src/store/searchSlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; 2 | import { BASE_URL } from "../utils/apiURL"; 3 | import { STATUS } from "../utils/status"; 4 | 5 | const initialState = { 6 | searchProducts: [], 7 | searchProductsStatus: STATUS.IDLE 8 | } 9 | 10 | const searchSlice = createSlice({ 11 | name: 'search', 12 | initialState, 13 | reducers: { 14 | clearSearch: (state, action) => { 15 | state.searchProducts = []; 16 | } 17 | }, 18 | extraReducers: (builder) => { 19 | builder 20 | .addCase(fetchAsyncSearchProduct.pending, (state, action) => { 21 | state.searchProductsStatus = STATUS.LOADING; 22 | }) 23 | 24 | .addCase(fetchAsyncSearchProduct.fulfilled, (state, action) => { 25 | state.searchProducts = action.payload; 26 | state.searchProductsStatus = STATUS.SUCCEEDED; 27 | }) 28 | 29 | .addCase(fetchAsyncSearchProduct.rejected, (state, action) => { 30 | state.searchProductsStatus = STATUS.FAILED; 31 | }) 32 | } 33 | }) 34 | 35 | export const fetchAsyncSearchProduct = createAsyncThunk('product-search/fetch', async(searchTerm) => { 36 | const response = await fetch(`${BASE_URL}products/search?q=${searchTerm}`); 37 | const data = await response.json(); 38 | return data.products; 39 | }); 40 | 41 | export const { setSearchTerm, clearSearch } = searchSlice.actions; 42 | export const getSearchProducts = (state) => state.search.searchProducts; 43 | export const getSearchProductsStatus = (state) => state.search.searchProductsStatus; 44 | export default searchSlice.reducer; -------------------------------------------------------------------------------- /src/store/sidebarSlice.js: -------------------------------------------------------------------------------- 1 | import {createSlice} from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | isSidebarOn: false 5 | } 6 | 7 | const sidebarSlice = createSlice({ 8 | name: 'sidebar', 9 | initialState, 10 | reducers: { 11 | setSidebarOn: (state) => { 12 | state.isSidebarOn = true; 13 | }, 14 | 15 | setSidebarOff: (state) => { 16 | state.isSidebarOn = false; 17 | } 18 | }, 19 | }); 20 | 21 | export const {setSidebarOn, setSidebarOff} = sidebarSlice.actions; 22 | export const getSidebarStatus = (state) => state.sidebar.isSidebarOn; 23 | export default sidebarSlice.reducer; -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import {configureStore} from "@reduxjs/toolkit"; 2 | import sidebarReducer from "./sidebarSlice"; 3 | import categoryReducer from "./categorySlice"; 4 | import productReducer from "./productSlice"; 5 | import cartReducer from "./cartSlice"; 6 | import searchReducer from "./searchSlice"; 7 | 8 | const store = configureStore({ 9 | reducer: { 10 | sidebar: sidebarReducer, 11 | category: categoryReducer, 12 | product: productReducer, 13 | cart: cartReducer, 14 | search: searchReducer 15 | } 16 | }); 17 | 18 | export default store; -------------------------------------------------------------------------------- /src/utils/apiURL.js: -------------------------------------------------------------------------------- 1 | export const BASE_URL = 'https://dummyjson.com/'; -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabinmagar/snapup-commerce-site-react-js/6825029e2329d0fe964ebccdd944e709013061d1/src/utils/constants.js -------------------------------------------------------------------------------- /src/utils/helpers.js: -------------------------------------------------------------------------------- 1 | export const formatPrice = (price) => { 2 | return new Intl.NumberFormat('en-US', { 3 | style: 'currency', 4 | currency: "USD" 5 | }).format(price); 6 | } -------------------------------------------------------------------------------- /src/utils/images.js: -------------------------------------------------------------------------------- 1 | import slider_img_1 from "../assets/images/slider_img_1.jpg"; 2 | import slider_img_2 from "../assets/images/slider_img_2.jpg"; 3 | import shopping_cart from "../assets/images/shopping_cart.png"; 4 | import correct from "../assets/images/correct.png"; 5 | import loader from "../assets/images/loader.svg"; 6 | 7 | const sliderImgs = [slider_img_1, slider_img_2]; 8 | export {sliderImgs, shopping_cart, correct, loader}; -------------------------------------------------------------------------------- /src/utils/status.js: -------------------------------------------------------------------------------- 1 | export const STATUS = Object.freeze({ 2 | IDLE: 'IDLE', 3 | FAILED: 'FAILED', 4 | LOADING: 'LOADING', 5 | SUCCEEDED: 'SUCCEEDED' 6 | }); --------------------------------------------------------------------------------