├── .firebase
└── hosting.YnVpbGQ.cache
├── .firebaserc
├── .gitignore
├── README.md
├── firebase.json
├── functions
├── .eslintrc.json
├── .gitignore
├── index.js
├── package-lock.json
└── package.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.js
├── App.test.js
├── app.scss
├── assets
│ ├── add_shopping_cart.svg
│ ├── auth-logo.png
│ ├── cart.svg
│ ├── header-logo.png
│ ├── home-bg-alexa.jpg
│ ├── home-bg-computer.jpg
│ ├── home-bg-gift.jpg
│ ├── home-bg-nz.jpg
│ ├── home-bg-prime.jpg
│ ├── home-bg-ship.jpg
│ ├── menu.svg
│ ├── place.svg
│ ├── search-icon.svg
│ ├── star.svg
│ ├── star_border.svg
│ └── start_half.svg
├── axios.js
├── components
│ ├── card
│ │ ├── Card.jsx
│ │ ├── Card.scss
│ │ ├── CheckoutCard.jsx
│ │ ├── CheckoutCard.scss
│ │ ├── ProductCard.jsx
│ │ └── ProductCard.scss
│ ├── carousel
│ │ ├── Carousel.jsx
│ │ └── Carousel.scss
│ ├── footer
│ │ ├── Footer.jsx
│ │ └── Footer.scss
│ ├── header
│ │ ├── header.jsx
│ │ └── header.scss
│ ├── home
│ │ ├── BestSellers.jsx
│ │ ├── BestSellers.scss
│ │ ├── PopularSection.jsx
│ │ ├── PopularSection.scss
│ │ ├── RecommendSection.jsx
│ │ ├── RecommendSection.scss
│ │ ├── RelatedSection.jsx
│ │ ├── ShopBySection.jsx
│ │ └── ShopBySection.scss
│ ├── link
│ │ ├── RouterLink.jsx
│ │ └── RouterLink.scss
│ ├── order
│ │ ├── Order.jsx
│ │ └── Order.scss
│ └── shop
│ │ ├── ShopPage.jsx
│ │ └── ShopPage.scss
├── firebase.utils.js
├── index.js
├── layouts
│ ├── AuthLayout
│ │ ├── AuthLayout.scss
│ │ └── index.js
│ └── MainLayout
│ │ ├── MainLayout.scss
│ │ └── index.js
├── mockup.js
├── redux
│ ├── cartSelector.js
│ ├── cartSlice.js
│ ├── productSelector.js
│ ├── productsSlice.js
│ ├── store.js
│ └── userSlice.js
├── routes.js
├── scss
│ ├── _basic.scss
│ ├── _mixins.scss
│ └── _variables.scss
├── serviceWorker.js
├── setupTests.js
└── views
│ ├── auth
│ ├── Login.jsx
│ ├── Login.scss
│ ├── Register.jsx
│ └── Register.scss
│ ├── checkout
│ ├── Checkout.jsx
│ └── Checkout.scss
│ ├── errors
│ └── NotFoundView.js
│ ├── home
│ ├── home.jsx
│ └── home.scss
│ ├── orders
│ ├── Orders.jsx
│ └── Orders.scss
│ ├── payment
│ ├── Payment.jsx
│ ├── Payment.scss
│ └── Success.jsx
│ ├── product
│ ├── Product.jsx
│ └── Product.scss
│ └── shop
│ ├── shop.jsx
│ └── shop.scss
└── yarn.lock
/.firebase/hosting.YnVpbGQ.cache:
--------------------------------------------------------------------------------
1 | asset-manifest.json,1606266629988,2d9a7020a38c1ae8715b72c0ec82f5f9a9ebdae99c37232e18610a0519ec61e1
2 | index.html,1606266629986,1387f9c095dfea6f2982471129158ab4e9a0dd90ff5de7ce13d2ee7256491445
3 | favicon.ico,1605145252532,d96ddbc4933b04e12c738ab39f469573143949ca2c39eda0a49d16f83d40c319
4 | manifest.json,1605145252532,5c997de1364b8be939319fa9209abd77f2caf7f8844999a9e2e9173f844e7840
5 | logo192.png,1605145252532,3ee59515172ee198f3be375979df15ac5345183e656720a381b8872b2a39dc8b
6 | robots.txt,1605145252532,b2090cf9761ef60aa06e4fab97679bd43dfa5e5df073701ead5879d7c68f1ec5
7 | precache-manifest.1cc78e8194080b1723486cec36e4d137.js,1606266629987,37cdbb6d2c0bd5aa16f80390d40f756169e7ef5e6455e988547659692ed1bf70
8 | service-worker.js,1606266629987,48f8044014129ea1cc64900c781efbb6e5ae1b4581ff8dd8ddcf63ab6c8281fd
9 | static/css/main.5a749ae4.chunk.css,1606266629961,e35b69e1d12c37b59ef8c6e65caae53151e1778d4d876fe798a284df8a687316
10 | logo512.png,1605145252532,ee7e2f3fdb8209c4b6fd7bef6ba50d1b9dba30a25bb5c3126df057e1cb6f5331
11 | static/css/main.5a749ae4.chunk.css.map,1606266629986,fb38a6a115da870602c1a6bd252bdcc90df4dbe4432fa94524b79aa6cab65873
12 | static/js/2.e4f6a30c.chunk.js.LICENSE.txt,1606266629985,f4c4cfb51c4344d8e4b70e60f0d080d512d460172db8050964fb5013d7d8d183
13 | static/js/runtime-main.7006a95e.js,1606266629985,b1a9c4519d1da00d405553d197bf302fa160921ff5bf4fd921d0770ef0a355af
14 | static/js/runtime-main.7006a95e.js.map,1606266629985,454af535b516dd224aec3a3d183da3395ee6ef75b88fd0f3718882175aaea9df
15 | static/media/add_shopping_cart.bba0d047.svg,1606266629984,d384167b7e4a88dab543f877c65782b3fde9328c273c6dadf888fee38394ceef
16 | static/media/cart.84c5fa25.svg,1606266629963,70e034dd9a801aa90bbde39b2dff52538141850e59c472248246f9493146e489
17 | static/media/menu.55f68494.svg,1606266629953,fdddffcdd481562b185b38d79157793f03b55e3f7468a23fe0333bce23c02e20
18 | static/media/place.09425351.svg,1606266629984,cb99358d05dfb9e6208029a25ff8d748cccba09c4b2264f49038065546ddaa3a
19 | static/media/search-icon.db5d8122.svg,1606266629956,77dfa9bbdf380baa793b4664b2178b0aa706cfe36ea77154f924777ba91c1744
20 | static/media/slick.b7c9e1e4.woff,1606266629950,029a727c9e1081d2f9e1015f02f18da88f268512ea4f58589a6f17f532928530
21 | static/media/slick.f97e3bbf.svg,1606266629952,d00477d40ed40fa1dd3bdee27d9286804121b15fea83bae7d5415d902b055de0
22 | static/media/slick.d41f55a7.ttf,1606266629952,6aad751007cf9a89350af287f824ab64f096c472c7c9c679fb3d55b6174fc004
23 | static/media/star.555f59ec.svg,1606266629953,a294f099e4233acead5b1c7021c647f95b8a7d3da5dd3a6554a189f04f47509f
24 | static/media/start_half.71db7c4e.svg,1606266629953,756e87211bf273d2fb7a8dbee001ca4686536ed36d976b00140a39f464052f72
25 | static/media/star_border.e8c07bf4.svg,1606266629953,998a24820b96a893de008b62c1904840a843e273ebff83ac4439b80ed8c5fb76
26 | static/media/auth-logo.f07041c4.png,1606266629953,e8be3194bc336bc5ee9ff7cd8cdc2be1a150c8915dc981a83d20bbd8b4b08210
27 | static/media/home-bg-nz.38761ed4.jpg,1606266629953,81ea1a68dcf8eee951db3690e6e121a6dfec01120e7162b4eb28a8e1deaf2ed8
28 | static/media/slick.ced611da.eot,1606266629952,52b9a169969e053d43df6bd21c4dc789c2518d99afd8590a17ef50cd24c77cf6
29 | static/js/main.2a595da6.chunk.js,1606266629961,f7f64b1f554eb25b3655a0f295f1e7ea526d3dae6b9d7b6ee53e1632c44fa3b5
30 | static/media/home-bg-ship.1b10c93f.jpg,1606266629953,9c87cdf43341a95d83d28f7920cc04b1e9f425f13f38b5ad83704df15ce4988e
31 | static/media/home-bg-computer.6aafad0c.jpg,1606266629953,c4ee3bb97162a4e21269ab791108a2d665c3b6d94e7225ff3afde6c9cf551942
32 | static/media/header-logo.7451f65b.png,1606266629953,9c2857d0056bec3cfb4e789b19eb576703f565589ebc39a6dd56afc494c090b8
33 | static/media/home-bg-prime.80469a24.jpg,1606266629952,b6b0f92d90ff7db059c0d6379b7963c554b2775eeb026d0c1270b549e25c9acb
34 | static/media/home-bg-alexa.87b0e88b.jpg,1606266629953,8d3d277fad4370cae47ed0733b0998ac83cbd51e56c41a26858cc6c256abbb42
35 | static/js/main.2a595da6.chunk.js.map,1606266629985,7dfe7c6a78c4c4568c98ff564564c352e8b5e83234fc6b0fd109184e2dc275c8
36 | static/css/2.8c03b463.chunk.css,1606266629985,cc61bdf46d88906ada16ec92f758038cdb696a30b6a002a14b718a32d2ff452c
37 | static/js/2.e4f6a30c.chunk.js,1606266629985,d150163fde6b9438ad28228b26869b0b6f84a751f0f7af570e49a3470f8995d1
38 | static/css/2.8c03b463.chunk.css.map,1606266629986,e804e652bcf479b1e341d8ce81afd437c1b38bf2249e1fa5849a798ddcacc387
39 | static/js/2.e4f6a30c.chunk.js.map,1606266629987,a7d8816b4fd752aeafc0c2b76b42d8d4c588a1c4eae3a8c59c65108058ea2107
40 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "github-c5c88"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 |
107 | build/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## [Amazon Clone](https://github-c5c88.firebaseapp.com/)
2 |
3 | - Views are on: https://github-c5c88.firebaseapp.com
4 |
5 | [](https://github-c5c88.firebaseapp.com/)
6 | > This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
7 | It is a fully functional E-Commerical Amazon clone including user register, user login, user authentication, shop cart management, order management, and payment using STRIPE, etc.
8 | ## Technical Skills used in this project
9 |
10 | | Front End | Back End |
11 | | ------------------------ | :----------------------------------------------------------- |
12 | | ✔ react hooks | ✔ NodeJS
13 | | ✔ react redux |✔ Express / cors
14 | | ✔ react router |✔ Authtentication with Firebase
15 | | ✔ redux toolkit |✔ Firebase Cloud Function
16 | | ✔ redux-devtools-extension | ✔ Firebase Hosting
17 | | ✔ immer | ✔ Realtime Database / Cloud Firestore
18 | | ✔ reselect | ✔ Stripe-js
19 | | ✔ redux-thunk | -
20 | | ✔ redux-persist | -
21 | | ✔ formik | -
22 | | ✔ SASS | -
23 | | ✔ react bootstrap / AntDesign | -
24 |
25 | ## Demo
26 |
27 | - [Home Page](https://github-c5c88.firebaseapp.com/)
28 | - [Register Page](https://github-c5c88.firebaseapp.com/auth/register)
29 | - [Login Page](https://github-c5c88.firebaseapp.com/auth)
30 | - [Product Page](https://github-c5c88.firebaseapp.com/product/hDZ4xItnMf0l3YT17uY5)
31 | - [Cart Page](https://github-c5c88.firebaseapp.com/checkout)
32 | - [Checkout Page](https://github-c5c88.firebaseapp.com/checkout)
33 | - [Payment Page](https://github-c5c88.firebaseapp.com/payment)
34 | - [Order Page](https://github-c5c88.firebaseapp.com/orders)
35 | - [Payment API Endpoint](https://us-central1-github-c5c88.cloudfunctions.net/amazonStripeApi)
36 | - [Stripe Payments Show](https://firebasestorage.googleapis.com/v0/b/github-c5c88.appspot.com/o/appScreenshot%2Famazon-payment.png?alt=media&token=ea6245b6-9a90-4709-a10d-8023aeac72b7)
37 | [](https://firebasestorage.googleapis.com/v0/b/github-c5c88.appspot.com/o/appScreenshot%2Famazon-payment.png?alt=media&token=ea6245b6-9a90-4709-a10d-8023aeac72b7)
38 |
39 | ## Quick start
40 |
41 | - [Download from Github](https://github.com/aaroncodehub/amazon-clone/archive/master.zip) or clone the repo: `git clone https://github.com/aaroncodehub/amazon-clone.git`
42 |
43 | - Make sure your NodeJS and npm versions are up to date for `16.13.1`
44 |
45 | - Install dependencies: `npm install` or `yarn`
46 |
47 | - Start the server: `npm run start` or `yarn start`
48 |
49 | - Views are on: `localhost:3000`
50 |
51 | - Payment API Endpoint: `https://us-central1-github-c5c88.cloudfunctions.net/amazonStripeApi` or
Get a local endpoint: `firebase emulators:start`
52 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": {
3 | "predeploy": [
4 | "npm --prefix \"$RESOURCE_DIR\" run lint"
5 | ],
6 | "source": "functions"
7 | },
8 | "hosting": {
9 | "public": "build",
10 | "ignore": [
11 | "firebase.json",
12 | "**/.*",
13 | "**/node_modules/**"
14 | ],
15 | "rewrites": [
16 | {
17 | "source": "**",
18 | "destination": "/index.html"
19 | }
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/functions/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | // Required for certain syntax usages
4 | "ecmaVersion": 2017
5 | },
6 | "plugins": [
7 | "promise"
8 | ],
9 | "extends": "eslint:recommended",
10 | "rules": {
11 | // Removed rule "disallow the use of console" from recommended eslint rules
12 | "no-console": "off",
13 |
14 | // Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules
15 | "no-regex-spaces": "off",
16 |
17 | // Removed rule "disallow the use of debugger" from recommended eslint rules
18 | "no-debugger": "off",
19 |
20 | // Removed rule "disallow unused variables" from recommended eslint rules
21 | "no-unused-vars": "off",
22 |
23 | // Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules
24 | "no-mixed-spaces-and-tabs": "off",
25 |
26 | // Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules
27 | "no-undef": "off",
28 |
29 | // Warn against template literal placeholder syntax in regular strings
30 | "no-template-curly-in-string": 1,
31 |
32 | // Warn if return statements do not either always or never specify values
33 | "consistent-return": 1,
34 |
35 | // Warn if no return statements in callbacks of array methods
36 | "array-callback-return": 1,
37 |
38 | // Require the use of === and !==
39 | "eqeqeq": 2,
40 |
41 | // Disallow the use of alert, confirm, and prompt
42 | "no-alert": 2,
43 |
44 | // Disallow the use of arguments.caller or arguments.callee
45 | "no-caller": 2,
46 |
47 | // Disallow null comparisons without type-checking operators
48 | "no-eq-null": 2,
49 |
50 | // Disallow the use of eval()
51 | "no-eval": 2,
52 |
53 | // Warn against extending native types
54 | "no-extend-native": 1,
55 |
56 | // Warn against unnecessary calls to .bind()
57 | "no-extra-bind": 1,
58 |
59 | // Warn against unnecessary labels
60 | "no-extra-label": 1,
61 |
62 | // Disallow leading or trailing decimal points in numeric literals
63 | "no-floating-decimal": 2,
64 |
65 | // Warn against shorthand type conversions
66 | "no-implicit-coercion": 1,
67 |
68 | // Warn against function declarations and expressions inside loop statements
69 | "no-loop-func": 1,
70 |
71 | // Disallow new operators with the Function object
72 | "no-new-func": 2,
73 |
74 | // Warn against new operators with the String, Number, and Boolean objects
75 | "no-new-wrappers": 1,
76 |
77 | // Disallow throwing literals as exceptions
78 | "no-throw-literal": 2,
79 |
80 | // Require using Error objects as Promise rejection reasons
81 | "prefer-promise-reject-errors": 2,
82 |
83 | // Enforce “for” loop update clause moving the counter in the right direction
84 | "for-direction": 2,
85 |
86 | // Enforce return statements in getters
87 | "getter-return": 2,
88 |
89 | // Disallow await inside of loops
90 | "no-await-in-loop": 2,
91 |
92 | // Disallow comparing against -0
93 | "no-compare-neg-zero": 2,
94 |
95 | // Warn against catch clause parameters from shadowing variables in the outer scope
96 | "no-catch-shadow": 1,
97 |
98 | // Disallow identifiers from shadowing restricted names
99 | "no-shadow-restricted-names": 2,
100 |
101 | // Enforce return statements in callbacks of array methods
102 | "callback-return": 2,
103 |
104 | // Require error handling in callbacks
105 | "handle-callback-err": 2,
106 |
107 | // Warn against string concatenation with __dirname and __filename
108 | "no-path-concat": 1,
109 |
110 | // Prefer using arrow functions for callbacks
111 | "prefer-arrow-callback": 1,
112 |
113 | // Return inside each then() to create readable and reusable Promise chains.
114 | // Forces developers to return console logs and http calls in promises.
115 | "promise/always-return": 2,
116 |
117 | //Enforces the use of catch() on un-returned promises
118 | "promise/catch-or-return": 2,
119 |
120 | // Warn against nested then() or catch() statements
121 | "promise/no-nesting": 1
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/functions/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | *.log
--------------------------------------------------------------------------------
/functions/index.js:
--------------------------------------------------------------------------------
1 | const functions = require("firebase-functions");
2 |
3 | // // Create and Deploy Your First Cloud Functions
4 | // // https://firebase.google.com/docs/functions/write-firebase-functions
5 | //
6 | // exports.helloWorld = functions.https.onRequest((request, response) => {
7 | // response.send("Hello from Firebase!");
8 | // });
9 |
10 | // set env vars: gcloud functions deploy FUNCTION_NAME --set-env-vars FOO=bar , get env vars : process.env.FOO
11 |
12 | const express = require("express");
13 | const cors = require("cors");
14 | const stripe = require("stripe")(
15 | process.env.STRIPE_KEY
16 | );
17 |
18 | // - App Config
19 | const app = express();
20 |
21 | // - Middlrwares
22 | app.use(cors());
23 | app.use(express.json());
24 |
25 | // - API Routes
26 | app.get("/", (req, res) => res.status(200).send("welcome"));
27 |
28 | app.post("/payments/create/:total", async (req, res) => {
29 |
30 | if (req.params.total > 0) {
31 | // only accept integer
32 | const total = parseFloat(req.params.total).toFixed(0);
33 | console.log("Payment request received. Amount ->>>", total);
34 |
35 | const paymentIntent = await stripe.paymentIntents.create({
36 | amount: total,
37 | currency: "usd",
38 | });
39 | res.status(201).send({
40 | clientSecret: paymentIntent.client_secret,
41 | });
42 | } else {
43 | res.status(201).send({
44 | clientSecret: 'no session id',
45 | })
46 | }
47 | });
48 |
49 | // - Listen Command
50 | exports.amazonStripeApi = functions.https.onRequest(app);
51 |
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "description": "Cloud Functions for Firebase",
4 | "scripts": {
5 | "lint": "eslint .",
6 | "serve": "firebase emulators:start --only functions",
7 | "shell": "firebase functions:shell",
8 | "start": "npm run shell",
9 | "deploy": "firebase deploy --only functions",
10 | "logs": "firebase functions:log"
11 | },
12 | "engines": {
13 | "node": "12"
14 | },
15 | "dependencies": {
16 | "cors": "^2.8.5",
17 | "express": "^4.17.1",
18 | "firebase-admin": "^8.10.0",
19 | "firebase-functions": "^3.6.1",
20 | "stripe": "^8.115.0"
21 | },
22 | "devDependencies": {
23 | "eslint": "^5.12.0",
24 | "eslint-plugin-promise": "^4.0.1",
25 | "firebase-functions-test": "^0.2.0"
26 | },
27 | "private": true
28 | }
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "amazon-cl",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ant-design/icons": "^4.3.0",
7 | "@brainhubeu/react-carousel": "2.0.1",
8 | "@reduxjs/toolkit": "^1.4.0",
9 | "@stripe/react-stripe-js": "^1.1.2",
10 | "@stripe/stripe-js": "^1.10.0",
11 | "@testing-library/jest-dom": "^4.2.4",
12 | "@testing-library/react": "^9.3.2",
13 | "@testing-library/user-event": "^7.1.2",
14 | "antd": "^4.8.4",
15 | "axios": "^0.20.0",
16 | "bootstrap": "^4.5.2",
17 | "firebase": "^7.23.0",
18 | "formik": "^2.2.0",
19 | "history": "^5.0.0",
20 | "moment": "^2.29.1",
21 | "node-sass": "^4.14.1",
22 | "react": "^16.13.1",
23 | "react-bootstrap": "^1.3.0",
24 | "react-currency-format": "^1.0.0",
25 | "react-dom": "^16.13.1",
26 | "react-image-gallery": "^1.0.8",
27 | "react-redux": "^7.2.1",
28 | "react-router-dom": "^6.0.0-beta.0",
29 | "react-scripts": "3.4.3",
30 | "react-slick": "^0.27.13",
31 | "redux-persist": "^6.0.0",
32 | "slick-carousel": "^1.8.1",
33 | "uuid": "^8.3.1",
34 | "yup": "^0.29.3"
35 | },
36 | "scripts": {
37 | "start": "react-scripts start",
38 | "build": "react-scripts build",
39 | "test": "react-scripts test",
40 | "eject": "react-scripts eject"
41 | },
42 | "eslintConfig": {
43 | "extends": "react-app"
44 | },
45 | "browserslist": {
46 | "production": [
47 | ">0.2%",
48 | "not dead",
49 | "not op_mini all"
50 | ],
51 | "development": [
52 | "last 1 chrome version",
53 | "last 1 firefox version",
54 | "last 1 safari version"
55 | ]
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Amazon Clone
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/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 React, { useEffect } from "react";
2 | import { useRoutes } from "react-router-dom";
3 | import { useDispatch } from "react-redux";
4 | import { auth } from "./firebase.utils";
5 | import routes from "./routes";
6 | import "./app.scss";
7 | import { setUser, fetchUser } from "./redux/userSlice";
8 |
9 | function App() {
10 | const dispatch = useDispatch();
11 |
12 | useEffect(() => {
13 | const unsubscribe = auth.onAuthStateChanged((authUser) => {
14 | if (authUser) {
15 | dispatch(fetchUser(authUser.uid));
16 | } else {
17 | dispatch(setUser(null));
18 | }
19 | });
20 | return () => {
21 | unsubscribe();
22 | };
23 | }, [dispatch]);
24 |
25 | const routing = useRoutes(routes);
26 | return {routing}
;
27 | }
28 |
29 | export default App;
30 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render();
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/app.scss:
--------------------------------------------------------------------------------
1 | @import "./scss/basic";
2 | @import "./scss/variables";
3 | @import "./scss/mixins";
4 | @import "./views/home/home.scss";
5 | @import "./views/shop/shop.scss";
6 | @import "./components/header/header.scss";
7 | @import "./components/card/Card.scss";
8 | @import "./components/card/ProductCard.scss";
9 | @import "./components/home/ShopBySection.scss";
10 | @import "./components/carousel/Carousel.scss";
11 | @import "./components/shop/ShopPage.scss";
12 | @import './components/footer/Footer.scss';
13 | @import './views/auth/Register.scss';
14 | @import './views/auth/Login.scss';
15 | @import './layouts/MainLayout/MainLayout.scss';
16 | @import './layouts/AuthLayout/AuthLayout.scss';
17 | @import './views/checkout/Checkout.scss';
18 | @import './components/card/CheckoutCard.scss';
19 | @import './views/product/Product.scss';
20 | @import './views/payment/Payment.scss';
21 | @import './components/order/Order.scss';
22 | @import './views/orders/Orders.scss';
23 | @import './views/payment/Payment.scss';
24 | @import './components/carousel/Carousel.scss';
25 |
--------------------------------------------------------------------------------
/src/assets/add_shopping_cart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/auth-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/assets/auth-logo.png
--------------------------------------------------------------------------------
/src/assets/cart.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
51 |
--------------------------------------------------------------------------------
/src/assets/header-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/assets/header-logo.png
--------------------------------------------------------------------------------
/src/assets/home-bg-alexa.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/assets/home-bg-alexa.jpg
--------------------------------------------------------------------------------
/src/assets/home-bg-computer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/assets/home-bg-computer.jpg
--------------------------------------------------------------------------------
/src/assets/home-bg-gift.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/assets/home-bg-gift.jpg
--------------------------------------------------------------------------------
/src/assets/home-bg-nz.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/assets/home-bg-nz.jpg
--------------------------------------------------------------------------------
/src/assets/home-bg-prime.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/assets/home-bg-prime.jpg
--------------------------------------------------------------------------------
/src/assets/home-bg-ship.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/assets/home-bg-ship.jpg
--------------------------------------------------------------------------------
/src/assets/menu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/place.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/search-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/star.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/star_border.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/start_half.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/axios.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const instance = axios.create({
4 | baseURL:'https://us-central1-github-c5c88.cloudfunctions.net/amazonStripeApi'
5 | })
6 |
7 | export const source = axios.CancelToken.source();
8 |
9 | export default instance;
--------------------------------------------------------------------------------
/src/components/card/Card.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Card, Button } from "react-bootstrap";
3 | import RouterLink from "../link/RouterLink";
4 |
5 | const SharedCard = ({ title, imgUrl, linkInfo }) => {
6 | return (
7 |
8 |
9 |
10 |
11 | {title}
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default SharedCard;
22 |
--------------------------------------------------------------------------------
/src/components/card/Card.scss:
--------------------------------------------------------------------------------
1 | .card {
2 | min-width: 320px !important;
3 | width: 320px;
4 | }
--------------------------------------------------------------------------------
/src/components/card/CheckoutCard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form } from "react-bootstrap";
3 | import { useDispatch } from "react-redux";
4 | import { updateItemQuantity, removeItem } from "../../redux/cartSlice";
5 | import RouterLink from "../link/RouterLink";
6 |
7 | const CheckoutCard = ({
8 | imgUrl,
9 | price,
10 | productId,
11 | active,
12 | title,
13 | quantity,
14 | hiddenAction
15 | }) => {
16 | const dispatch = useDispatch();
17 | const formatedPrice = price.toLocaleString("en-US", {
18 | style: "currency",
19 | currency: "USD",
20 | });
21 |
22 | const link = `/product/${productId}`
23 |
24 | const updateQuantity = (e) => {
25 | const itemQuantity = e.target.value;
26 | dispatch(
27 | updateItemQuantity({ productId, quantity: parseInt(itemQuantity) })
28 | );
29 | };
30 |
31 | const removeCartItem = () => {
32 | dispatch(removeItem(productId));
33 | };
34 |
35 | return (
36 |
37 |

38 |
39 |
40 |
41 |
42 | {title}
43 |
44 |
45 |
46 | {active ? In stock : Out of stock}
47 |
48 |
49 | Enable for FREE Delivery
50 |
51 | {
52 | !hiddenAction &&
53 |
54 |
56 |
62 |
63 |
64 |
65 |
66 | |
67 | Delete
68 |
69 |
70 | }
71 |
72 |
73 | {formatedPrice}
74 |
75 |
76 |
77 | );
78 | };
79 |
80 | export default CheckoutCard;
81 |
--------------------------------------------------------------------------------
/src/components/card/CheckoutCard.scss:
--------------------------------------------------------------------------------
1 | .checkout-card {
2 | display: flex;
3 |
4 | &__img {
5 | @include breakpoint(l) {
6 | width: 8rem;
7 | margin: 1rem;
8 | }
9 | width: 8rem;
10 | object-fit: contain;
11 | margin: 1rem;
12 | }
13 | &__body {
14 | display: flex;
15 | padding-top: 1rem;
16 | flex-grow: 1;
17 | }
18 | &__title {
19 | font-size: 1.1rem;
20 | font-weight: 600;
21 | line-height: 1.2;
22 |
23 | color: $text-color-blue;
24 | }
25 | &__status {
26 | font-size: 0.8rem;
27 | color: green;
28 | font-weight: 500;
29 | }
30 | &__delivery {
31 | color: $text-color-grey;
32 | font-size: 0.8rem;
33 | font-weight: 600;
34 | }
35 | &__action {
36 | display: flex;
37 | align-items: center;
38 | &__qty {
39 | width: 4rem;
40 | margin-top: 0.5rem;
41 | }
42 | &__delete span:first-child {
43 | color: $line-color;
44 | padding-left: 1rem;
45 | }
46 | &__delete span:last-child {
47 | cursor: pointer;
48 | color: $text-color-blue;
49 | padding-left: 1rem;
50 | font-size: 0.8rem;
51 | font-weight: 600;
52 | }
53 | }
54 | &__price {
55 | font-weight: 600;
56 | @include breakpoint(l) {
57 | padding-left: 6rem;
58 | }
59 | padding-left: .6rem;
60 | }
61 | &__content {
62 | flex-grow: 1;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/card/ProductCard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ReactComponent as Star } from "../../assets/star.svg";
3 | import { ReactComponent as StarBorder } from "../../assets/star_border.svg";
4 | import { ReactComponent as StarHalf } from "../../assets/start_half.svg";
5 | import RouterLink from '../link/RouterLink'
6 |
7 |
8 | const ProductCard = ({ title, price, count, imgUrl, rate, productId}) => {
9 |
10 |
11 | const formatedPrice = price.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
12 | const splitPrice = formatedPrice.split('.')
13 |
14 | const link = `/product/${productId}`
15 |
16 | const renderStar = (rate) => {
17 | switch (rate) {
18 | case 1:
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | case 1.5:
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 | case 2:
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 | case 2.5:
49 | return (
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | case 3:
59 | return (
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | case 3.5:
69 | return (
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | );
78 | case 4:
79 | return (
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | );
88 | case 4.5:
89 | return (
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | );
98 | default:
99 | return (
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | );
108 | }
109 | };
110 |
111 | return (
112 |
113 |
114 |
115 |
116 |
{title}
117 |
118 | {splitPrice[0]}
119 | {splitPrice[1]}
120 |
121 |
122 | {renderStar(rate)}
123 | {`(${count})`}
124 |
125 |
126 |
127 |
128 |
129 | );
130 | };
131 |
132 | export default ProductCard;
133 |
--------------------------------------------------------------------------------
/src/components/card/ProductCard.scss:
--------------------------------------------------------------------------------
1 | .productCard {
2 | min-width: 12rem;
3 | max-width: 12rem;
4 | margin: 1rem;
5 | &__img {
6 | min-width: 10rem;
7 | max-width: 10rem;
8 | min-height: 10rem;
9 | max-height: 10rem;
10 | object-fit: contain;
11 | }
12 | &__body {
13 |
14 | &__title {
15 | font-weight: 600;
16 | font-size: smaller;
17 | height: 3rem;
18 |
19 | }
20 |
21 | &__text span:first-child {
22 | font-weight: 600;
23 | font-size: 1.3rem;
24 |
25 | }
26 |
27 | &__price{
28 | display: inline-block;
29 | transform: translateY(-.4rem);
30 | font-size: .8rem;
31 | padding-left: .1rem;
32 | }
33 | &__button {
34 |
35 | background-color: $button-color;
36 | border: 1px solid $button-border;
37 | color: $primary-color-light;
38 | padding: .1rem .5rem;
39 | margin: .5rem auto;
40 | border-radius: .2rem;
41 | width: 100%;
42 | }
43 | &__star {
44 | fill: $star-color;
45 | width: 1.2rem;
46 | & + span {
47 | margin-left: .5rem;
48 | }
49 | }
50 | &__count {
51 | padding-left: .5rem;
52 | }
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/src/components/carousel/Carousel.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Slider from "react-slick";
3 | import "slick-carousel/slick/slick.css";
4 | import "slick-carousel/slick/slick-theme.css";
5 | import { Card, Button } from "react-bootstrap";
6 | import "./Carousel.scss";
7 | import RouterLink from "./../link/RouterLink";
8 |
9 | const SharedCarousel = ({ title, imgs, alt, linkInfo,id }) => {
10 |
11 | let slidesToShow
12 | const width =
13 | window.innerWidth ||
14 | document.documentElement.clientWidth ||
15 | document.body.clientWidth;
16 | if (width > 768) {
17 | slidesToShow = 5
18 | } else if(width ===768) {
19 | slidesToShow = 4
20 | } else {
21 | slidesToShow =1
22 | }
23 |
24 | const settings = {
25 | infinite: true,
26 | speed: 500,
27 | slidesToShow,
28 | slidesToScroll: 1,
29 | };
30 |
31 |
32 | return (
33 |
34 |
35 |
36 | {title}
37 |
38 |
41 |
42 |
43 |
44 | {imgs.map((el, index) => (
45 |
46 |
47 |
52 |
53 |
54 | ))}
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default SharedCarousel;
62 |
--------------------------------------------------------------------------------
/src/components/carousel/Carousel.scss:
--------------------------------------------------------------------------------
1 | .carousel {
2 | width: 100%;
3 | margin: 1rem auto;
4 | &__item {
5 | display: flex !important;
6 | justify-content: space-around;
7 | flex-wrap: nowrap;
8 | &__img {
9 | margin: 0 1rem;
10 | height: 200px;
11 | }
12 | }
13 |
14 | &__title {
15 | display: flex;
16 | align-items: center;
17 | &__link {
18 | transform: translateY(-0.3rem);
19 | }
20 | }
21 | }
22 |
23 | .slick-slide img {
24 | display: block;
25 | margin: auto;
26 | }
27 |
28 | .slick-arrow {
29 | background-color: #eaeded !important;
30 | width: 2rem !important;
31 | height: 5rem !important;
32 | border-radius: 2px;
33 | box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.2);
34 | opacity: 0.8;
35 | }
36 |
37 | .slick-arrow:hover {
38 | background: #f1c964 !important;
39 | transition: background 0.6s;
40 | }
41 | .slick-prev {
42 | z-index: 100;
43 | left: 20px !important;
44 | }
45 | .slick-next {
46 | z-index: 100;
47 | right: 20px !important;
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/footer/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Footer = () => {
4 | return (
5 |
6 |
7 |
8 |
Get to Know Us
9 |
Careers
10 |
Blog
11 |
About Amazon
12 |
Investor Relations
13 |
Amazon Devices
14 |
Amazon Tours
15 |
16 |
17 |
Make Money with Us
18 |
Sell products on Amazon
19 |
Become an Affiliate
20 |
Advertise Your Products
21 |
Self-publish with US
22 |
Host an Amazon Hub
23 |
24 |
25 |
Amazon Payment Products
26 |
Amazon Business Card
27 |
Shop with Points
28 |
Reload Your Balance
29 |
Amazon Currency Converter
30 |
31 | {/*
32 |
Let Us Help You
33 |
Amazon and Covid-19
34 |
Your Account
35 |
Your Orders
36 |
Shipping Rates & Policies
37 |
Returns & Rplacement
38 |
Manage Your Content and Devices
39 |
Amazon Assistant
40 |
Help
41 |
*/}
42 |
43 |
44 |
45 | Conditions of Use
46 | Privacy Notice
47 | Interest-Based Ads
48 | © 2020, Amazon Clone By Aaron.
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default Footer;
56 |
--------------------------------------------------------------------------------
/src/components/footer/Footer.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | background-color: $primary-color-light;
3 | &__container {
4 | max-width: 1500px;
5 | color: $text-color;
6 | display: flex;
7 | justify-content: space-around;
8 | margin: 0 auto;
9 | padding: 2rem;
10 | gap: 1rem;
11 | }
12 |
13 | &__title {
14 | font-weight: 800;
15 | & ~ p {
16 | font-size: .9rem;
17 | }
18 | }
19 |
20 | &__copyright {
21 | color: $text-color;
22 | background-color: $primary-color-light;
23 | padding: 2rem;
24 | position: relative;
25 | &__info {
26 | transform: translate(-50%, -50%);
27 | margin: 0;
28 | position: absolute;
29 | top: 50%;
30 | left: 50%;
31 | & span {
32 | padding-right: 1rem;
33 | font-size: small;
34 | &:last-child {
35 | color: $text-color-grey;
36 | }
37 | }
38 | }
39 |
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/header/header.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { selectUser } from "../../redux/userSlice";
3 | import { useSelector } from "react-redux";
4 | import RouterLink from "../link/RouterLink";
5 | import { ReactComponent as Menu } from "../../assets/menu.svg";
6 | import { ReactComponent as SearchIcon } from "../../assets/search-icon.svg";
7 | import { ReactComponent as Cart } from "../../assets/cart.svg";
8 | import { ReactComponent as Place } from "../../assets/place.svg";
9 | import logo from "../../assets/header-logo.png";
10 | import { InputGroup, FormControl, Form } from "react-bootstrap";
11 | import { auth } from "../../firebase.utils";
12 | import { selectCartItemsCount } from "../../redux/cartSelector";
13 | import { useNavigate } from "react-router-dom";
14 | import { Drawer,Divider} from "antd";
15 |
16 |
17 |
18 | const Header = () => {
19 | const [showMenu, setShowMenu] = useState(false);
20 | const user = useSelector(selectUser);
21 | const cartCount = useSelector((state) => selectCartItemsCount(state));
22 |
23 |
24 | const navigate = useNavigate();
25 |
26 |
27 | const signOut = () => {
28 | if (user) auth.signOut();
29 | };
30 |
31 | const handleSearch = (e) => {
32 | e.preventDefault();
33 | navigate("/shop");
34 | };
35 | return (
36 | <>
37 | setShowMenu(false)}
43 | visible={showMenu}
44 | footer={
45 |
46 |
Help & Settings
47 |
Account
48 |
Customer Service
49 |
Sign Out
50 |
51 | }
52 | >
53 |
54 |
55 |
56 |
57 |
Treading
58 |
Best Sellers
59 |
New Releases
60 |
Movers and Shakers
61 |
62 |
63 |
Digital Content And Devices
64 |
Amazon Fire TV
65 |
Amazon Prime Video
66 |
Amazon Music
67 |
68 |
69 |
70 |
Shop By Department
71 |
Books
72 |
Music, Movies & Games
73 |
Computers
74 |
Electronics
75 |
Home
76 |
Baby
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
89 |
90 |
91 |

92 |
93 |
94 |
95 |
114 |
115 |
116 |
117 |
118 |
119 | Hello {user ? user.name : "Guest"}
120 | {user ? "Sign Out" : "Sign In"}
121 |
122 |
123 |
124 |
125 | returns
126 |
127 |
128 | &orders
129 |
130 |
131 |
132 |
133 | {cartCount}
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | Deliver to
145 | New Zealand
146 |
147 |
148 |
149 | Today's Deals
150 | Customer Service
151 |
152 |
153 | Gift Cards
154 | Registry
155 | Sell
156 |
157 |
158 | Amazon's response to COVID-19
159 |
160 |
161 |
162 | >
163 | );
164 | };
165 |
166 | export default Header;
167 |
--------------------------------------------------------------------------------
/src/components/header/header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | display: flex;
3 | padding: 0.5rem 1rem;
4 | background-color: $primary-color-main;
5 | align-items: center;
6 | &__menu {
7 | height: 3rem;
8 | &__icon {
9 | width: 2.5rem;
10 | height: 3rem;
11 | fill: $text-color;
12 | border: 0.1rem solid grey;
13 | border-radius: 0.2rem;
14 | transition: border 0.2s ease-in-out;
15 | &:hover {
16 | border: 0.1rem solid $text-color;
17 | }
18 | }
19 | }
20 |
21 | &__logo > img {
22 | width: 6rem;
23 | @include breakpoint(l) {
24 | margin: 0.5rem 2rem 0 0.5rem;
25 | }
26 | margin: 0 0.5rem;
27 | }
28 |
29 | &__search {
30 | display: none;
31 | flex-grow: 1;
32 | background-color: $secondary-color-main;
33 | border-radius: 0.2rem;
34 | @include breakpoint(m) {
35 | display: inline;
36 | }
37 |
38 | & button {
39 | display: none;
40 | }
41 |
42 | &__category {
43 | background-color: $background-color !important;
44 | width: 6rem !important;
45 | }
46 |
47 | &__icon {
48 | width: 2.3rem;
49 | height: 2.3rem;
50 | fill: $primary-color-light;
51 | padding: 0.5rem;
52 | }
53 | }
54 |
55 | &__nav {
56 | display: flex;
57 | margin: auto 1rem;
58 | &__option {
59 | @include breakpoint(m) {
60 | display: flex;
61 | }
62 | color: $text-color;
63 | display: none;
64 | flex-direction: column;
65 | margin: 0 0.5rem;
66 | transform: translateY(0.5rem);
67 | & span:first-child {
68 | font-size: 0.7rem;
69 | }
70 | & span:last-child {
71 | font-size: 0.8rem;
72 | font-weight: 800;
73 | }
74 | }
75 | &__cart {
76 | color: $secondary-color-dark;
77 | &__icon {
78 | width: 3.2rem;
79 | fill: $text-color;
80 | position: absolute;
81 | top: 0.5rem;
82 | right: 2rem;
83 | @include breakpoint(m) {
84 | position: relative;
85 | top: 0rem;
86 | right: 0rem;
87 | }
88 | }
89 | & > span {
90 | position: absolute;
91 | top: 1.05rem;
92 | right: 3.5rem;
93 | font-weight: 500;
94 | }
95 | }
96 | }
97 | }
98 |
99 | .sub-header {
100 | @include breakpoint(l) {
101 | display: flex;
102 | }
103 | background-color: $primary-color-light;
104 | display: none;
105 | align-items: center;
106 | padding: 0.2rem 1rem;
107 | &__deliver {
108 | display: flex;
109 | &__icon {
110 | fill: white;
111 | width: 2.5rem;
112 | height: 2.5rem;
113 | }
114 | &__info {
115 | @extend .header__nav__option;
116 | display: flex;
117 | }
118 | }
119 | &__link {
120 | flex-grow: 1;
121 | padding-left: 2rem;
122 | color: $text-color;
123 | & span {
124 | padding-left: 1.5rem;
125 | }
126 | }
127 |
128 | &__notice {
129 | color: $text-color;
130 | font-weight: 600;
131 | }
132 | }
133 | .menuFooter {
134 | margin-left: 1rem;
135 | font-weight: 500;
136 |
137 | & p:first-child {
138 | font-size: 1.2rem;
139 | font-weight: bold;
140 | }
141 | }
142 |
143 | .menu__body__title {
144 |
145 | font-size: 1.2rem;
146 | font-weight: bold;
147 | & ~ p {
148 | font-weight: 500;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/components/home/BestSellers.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SharedCarousel from "../carousel/Carousel";
3 | import { v4 as uuid } from "uuid";
4 | import { selectProducts } from "../../redux/productsSlice";
5 | import { useSelector } from "react-redux";
6 |
7 |
8 | const BestSellersSection = () => {
9 | const products = useSelector(selectProducts);
10 | const imgs = products
11 | .slice(13, 19)
12 | .map((img) => ({ id: img.id, alt: img.title, imgUrl: img.imgUrl }));
13 | const topSellers = [{
14 | id: uuid(),
15 | title: "Most Popular Sellers",
16 | linkInfo: "Shop now",
17 | imgs,
18 | }];
19 |
20 | return (
21 |
22 | {topSellers?.map(({ id, ...props }) => (
23 |
24 | ))}
25 |
26 | );
27 | };
28 |
29 | export default BestSellersSection;
30 |
--------------------------------------------------------------------------------
/src/components/home/BestSellers.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/components/home/BestSellers.scss
--------------------------------------------------------------------------------
/src/components/home/PopularSection.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SharedCarousel from "../carousel/Carousel";
3 | import { v4 as uuid } from "uuid";
4 | import { selectProducts } from "../../redux/productsSlice";
5 | import { useSelector } from "react-redux";
6 |
7 |
8 | const PopularSection = () => {
9 | const products = useSelector(selectProducts);
10 | const imgs = products
11 | .slice(8, 16)
12 | .map((img) => ({ id: img.id, alt: img.title, imgUrl: img.imgUrl }));
13 | const topSellers = [{
14 | id: uuid(),
15 | title: "Amazon Top Sellers",
16 | linkInfo: "Shop now",
17 | imgs,
18 | }];
19 |
20 | return (
21 |
22 | {topSellers?.map(({ id, ...props }) => (
23 |
24 | ))}
25 |
26 | );
27 | };
28 |
29 | export default PopularSection;
30 |
--------------------------------------------------------------------------------
/src/components/home/PopularSection.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/components/home/PopularSection.scss
--------------------------------------------------------------------------------
/src/components/home/RecommendSection.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SharedCard from "../card/Card";
3 | import { v4 as uuid } from 'uuid';
4 |
5 | const recommend = [
6 | {id:uuid(),
7 | title: "Find unique gifts",
8 | imgUrl:
9 | "https://images-na.ssl-images-amazon.com/images/G/01/US-hq/2019/img/Social_Shopping/B00YBC17FK_260._CB442485249_SY260_.jpg",
10 | linkInfo: "See more gift ideas",
11 | },
12 | {id:uuid(),
13 | title: "Inexpensive finds",
14 | imgUrl:
15 | "https://images-na.ssl-images-amazon.com/images/G/01/xba/BTF_US_All_Sep2019_1x._CB440956261_SY260_.jpg",
16 | linkInfo: "See more",
17 | },
18 | {id:uuid(),
19 | title: "New gift card designs",
20 | imgUrl:
21 | "https://images-na.ssl-images-amazon.com/images/G/01/gift-certificates/consumer/2019/NewGCs/New/NewGCs_catcard_260x260_amazon_egc_1._CB438911828_SY260_.jpg",
22 | linkInfo: "Shop the collection",
23 | },
24 | {id:uuid(),
25 | title: "Renewed electronics",
26 | imgUrl:
27 | "https://images-na.ssl-images-amazon.com/images/G/01/US-hq/2019/img/Certified_Refurbished/XCM_Manual_1170890_desktop_tiles_260x260_Certified_Refurbished_XCM_Manual_1170890_us_certified_refurbished_desktop_tiles_260x260_2_1557752691_jpg._CB464246644_SY260_.jpg",
28 | linkInfo: "See more from Renewed",
29 | },
30 | ];
31 |
32 | const RecommendSection = () => {
33 | return (
34 |
35 | {recommend.map(({ id, ...props }) => (
36 |
37 | ))}
38 |
39 | );
40 | };
41 |
42 | export default RecommendSection;
43 |
--------------------------------------------------------------------------------
/src/components/home/RecommendSection.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/components/home/RecommendSection.scss
--------------------------------------------------------------------------------
/src/components/home/RelatedSection.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SharedCarousel from "../carousel/Carousel";
3 | import { v4 as uuid } from "uuid";
4 | import { selectProducts } from "../../redux/productsSlice";
5 | import { useSelector } from "react-redux";
6 |
7 |
8 | const RelatedSection = () => {
9 | const products = useSelector(selectProducts);
10 | const imgs = products
11 | .slice(1, 7)
12 | .map((img) => ({ id: img.id, alt: img.title, imgUrl: img.imgUrl }));
13 | const topSellers = [{
14 | id: uuid(),
15 | title: "Related to Items You've Viewed",
16 | linkInfo: "See more",
17 | imgs,
18 | }];
19 |
20 | return (
21 |
22 | {topSellers?.map(({ id, ...props }) => (
23 |
24 | ))}
25 |
26 | );
27 | };
28 |
29 | export default RelatedSection;
30 |
--------------------------------------------------------------------------------
/src/components/home/ShopBySection.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Card, Button } from "react-bootstrap";
3 | import { selectUser } from "../../redux/userSlice.js";
4 | import { useSelector } from "react-redux";
5 | import { auth } from "../../firebase.utils";
6 | import { useNavigate } from "react-router-dom";
7 | import RouterLink from "./../link/RouterLink";
8 |
9 | const ShopBySection = () => {
10 | const user = useSelector(selectUser);
11 | const navigate = useNavigate();
12 |
13 | const signOut = (e) => {
14 | e.preventDefault();
15 | if (user) auth.signOut();
16 | };
17 | const signIn = (e) => {
18 | e.preventDefault();
19 | navigate("/auth");
20 | };
21 | return (
22 |
23 |
24 |
25 |
26 | AmazonBasics
27 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Shop by Category
39 |
40 |
41 |

45 |
Computers & Accessories
46 |
47 |
48 |

52 |
Video Games
53 |
54 |
55 |
56 |
57 |

61 |
Baby
62 |
63 |
64 |

68 |
Fashion
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | Get US $10 off
80 |
81 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | {user ? (
92 |
93 | Hi, {user?.name}
94 |
101 |
102 | ) : (
103 |
104 | Sign in for the best experience
105 |
112 |
113 | )}
114 |
115 |
116 |
117 | );
118 | };
119 |
120 | export default ShopBySection;
121 |
--------------------------------------------------------------------------------
/src/components/home/ShopBySection.scss:
--------------------------------------------------------------------------------
1 | .shop-by-section {
2 | display: flex;
3 | justify-content: space-between;
4 | flex-wrap: wrap;
5 | gap: 1rem;
6 |
7 |
8 | // &:after {
9 | // content: "";
10 | // flex: auto;
11 | // }
12 |
13 | &__card {
14 |
15 | height: 350px !important;
16 | overflow: hidden;
17 | }
18 | &__user {
19 | display: none !important;
20 | min-width: 320px !important;
21 | height: 350px !important;
22 | &__button {
23 | background-color: $button-color !important;
24 | border: 1px solid $button-border !important;
25 | color: $primary-color-light !important;
26 | }
27 |
28 | @include breakpoint(xxl) {
29 | display: inline !important;
30 | }
31 | }
32 |
33 | &__category {
34 | display: flex;
35 | justify-content: space-around;
36 |
37 | & p {
38 | font-size: smaller;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/link/RouterLink.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | const RouterLink = ({ children, ...props }) => {
5 | return {children};
6 | };
7 |
8 | export default RouterLink;
9 |
--------------------------------------------------------------------------------
/src/components/link/RouterLink.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/components/link/RouterLink.scss
--------------------------------------------------------------------------------
/src/components/order/Order.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import moment from "moment";
3 | import CheckoutCard from "./../card/CheckoutCard";
4 | import CurrencyFormat from "react-currency-format";
5 |
6 | const Order = ({ order }) => {
7 |
8 | return (
9 |
10 |
Order
11 |
{moment.unix(order.data.created).format("MMMM Do YYYY,h:mma")}
12 |
13 | Order ID: {order.id}
14 |
15 | {order.data.cartItems &&
16 | order.data.cartItems.map(({ productId, ...props }) => (
17 |
21 | ))}
22 |
(
29 |
30 |
Order Total : {value}
31 |
32 | )}
33 | />
34 |
35 | );
36 | };
37 |
38 | export default Order;
39 |
--------------------------------------------------------------------------------
/src/components/order/Order.scss:
--------------------------------------------------------------------------------
1 | .order {
2 | @include breakpoint(m){
3 |
4 | padding: 40px;
5 | margin: 20px 0;
6 | border: 1px solid lightgray;
7 | margin: 5px 0;
8 | padding: 10px;
9 | }
10 | margin-left: -3rem;
11 | position: relative;
12 | background-color: white;
13 |
14 | &__id {
15 | @include breakpoint(l){
16 | position: absolute;
17 | right: 20px;
18 | top: 40px;
19 | }
20 | }
21 | &__total {
22 | font-weight: 500;
23 | text-align: right;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/components/shop/ShopPage.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form } from "react-bootstrap";
3 | import { ReactComponent as Star } from "../../assets/star.svg";
4 | import { ReactComponent as StarBorder } from "../../assets/star_border.svg";
5 | import ProductCard from "../card/ProductCard";
6 |
7 | const ShopPage = ({ products, departments }) => {
8 | // Merge the products from different department
9 | // const allProducts = shopData
10 | // .map((el) => el.products)
11 | // .reduce((all, item) => [...all, ...item]);
12 |
13 | // remove duplated brand and seller
14 | const brands = products.map((el) => el.brand);
15 | const featuredBrands = [...new Set(brands)];
16 |
17 | const sellers = products.map((el) => el.seller);
18 | const featuredSellers = [...new Set(sellers)];
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 | Department
27 |
28 |
29 | {departments &&
30 | departments.map(({departmentName, id}) => (
31 |
36 |
37 |
38 |
39 | ))}
40 |
41 |
42 |
43 |
44 |
45 | From Our Brands
46 |
47 |
48 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Featured Brands
61 |
62 |
63 | {featuredBrands &&
64 | featuredBrands.map((brand, id) => (
65 |
70 |
71 |
72 |
73 | ))}
74 |
75 |
76 |
77 |
78 | Avg. Customer Review
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | & Up
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | & Up
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | & Up
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | & Up
111 |
112 |
113 |
114 |
115 | Price
116 |
117 |
118 | under $25
119 | $25 to $50
120 | $50 to $100
121 | $100 to $200
122 | $200 and above
123 |
124 |
125 |
126 |
127 |
128 | Seler
129 |
130 |
131 | {featuredSellers &&
132 | featuredSellers.map((seller, id) => (
133 |
138 |
139 |
140 |
141 | ))}
142 |
143 |
144 |
145 |
146 |
147 | Availability
148 |
149 |
150 |
155 |
156 |
157 |
158 |
163 |
164 |
165 |
166 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | {products.map(({ id, ...props }) => (
180 |
181 | ))}
182 |
183 |
184 | );
185 | };
186 |
187 | export default ShopPage;
188 |
--------------------------------------------------------------------------------
/src/components/shop/ShopPage.scss:
--------------------------------------------------------------------------------
1 | .shopPage {
2 | padding: 0.5rem 1rem;
3 | display: flex;
4 | justify-content: center;
5 | font-size: small;
6 | font-weight: 600;
7 | position: relative;
8 | background-color: $background-color-light;
9 | &__sideBar {
10 | display: none;
11 | @include breakpoint(m){
12 | display: inline-block;
13 | &::after {
14 | content: "";
15 | width: 0.1rem;
16 | height: 96%;
17 | background: $line-color;
18 | left: 16rem;
19 | top: 0.5rem;
20 | position: absolute;
21 | }
22 | }
23 | &__refinedBy__title {
24 | font-weight: 800;
25 | padding: 0.5rem 0;
26 | // make sure the space is fully occupied by content
27 | width: 16rem;
28 | }
29 |
30 | &__refinedBy__content {
31 | & a {
32 | display: block;
33 | }
34 | &__star {
35 | fill: $star-color;
36 | width: 1.3rem;
37 | }
38 |
39 | &--form {
40 | margin: 0 !important;
41 | }
42 | }
43 | }
44 |
45 | &__list {
46 | @include breakpoint(m){
47 | display: flex;
48 | flex-wrap: wrap;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/firebase.utils.js:
--------------------------------------------------------------------------------
1 | import firebase from 'firebase/app';
2 | import 'firebase/firestore';
3 | import 'firebase/auth';
4 |
5 |
6 |
7 | const firebaseConfig = {
8 | apiKey: "AIzaSyAmoYVi2rCFrFeJh4Vr9h1MMlOYgPt9VZI",
9 | authDomain: "github-c5c88.firebaseapp.com",
10 | databaseURL: "https://github-c5c88.firebaseio.com",
11 | projectId: "github-c5c88",
12 | storageBucket: "github-c5c88.appspot.com",
13 | messagingSenderId: "389956365199",
14 | appId: "1:389956365199:web:4bf736df934794516179fa",
15 | measurementId: "G-MNFMWVVQ26",
16 | };
17 | // Initialize Firebase
18 | firebase.initializeApp(firebaseConfig);
19 |
20 | export const db = firebase.firestore();
21 | export const auth = firebase.auth();
22 |
23 | export const createUserProfileDocument = async (userAuth, additionalData) => {
24 | if (!userAuth) return;
25 |
26 | const userRef = db.doc(`amazonUsers/${userAuth.user.uid}`);
27 | const { displayName, email } = userAuth.user;
28 |
29 |
30 | const snapShot = await userRef.get();
31 | if (snapShot.exists) return;
32 |
33 | try {
34 | await userRef.set({
35 | displayName,
36 | email,
37 | timestamp:firebase.firestore.FieldValue.serverTimestamp(),
38 | ...additionalData,
39 | });
40 | } catch (err) {
41 | console.log("error creating user", err.message);
42 | }
43 | // return userRef;
44 | };
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from "react-redux";
4 | import { PersistGate } from "redux-persist/integration/react";
5 | import { BrowserRouter } from "react-router-dom";
6 | import App from "./App";
7 | import {store, persistor} from "./redux/store";
8 | import "bootstrap/dist/css/bootstrap.min.css";
9 | import 'antd/dist/antd.css';
10 | import * as serviceWorker from "./serviceWorker";
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ,
22 | document.getElementById("root")
23 | );
24 |
25 | // If you want your app to work offline and load faster, you can change
26 | // unregister() to register() below. Note this comes with some pitfalls.
27 | // Learn more about service workers: https://bit.ly/CRA-PWA
28 | serviceWorker.unregister();
29 |
--------------------------------------------------------------------------------
/src/layouts/AuthLayout/AuthLayout.scss:
--------------------------------------------------------------------------------
1 | .auth-layout{
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | background-color: white;
6 | height: 100vh;
7 | &__img{
8 | width: 12rem;
9 | margin: 1rem auto -1rem auto;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/layouts/AuthLayout/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Outlet } from "react-router-dom";
3 | import logo from '../../assets/auth-logo.png'
4 |
5 |
6 | const AuthLayout = () => {
7 | return (
8 |
9 |

10 |
11 |
12 | )
13 | }
14 |
15 | export default AuthLayout
16 |
--------------------------------------------------------------------------------
/src/layouts/MainLayout/MainLayout.scss:
--------------------------------------------------------------------------------
1 | .main-layout__spainner {
2 | position: absolute;
3 | top:50%;
4 | right: 50%;
5 | }
--------------------------------------------------------------------------------
/src/layouts/MainLayout/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import Spinner from "react-bootstrap/Spinner";
3 | import { Outlet } from "react-router-dom";
4 | import Header from "../../components/header/header";
5 | import Footer from "../../components/footer/Footer";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import {
8 | fetchProducts,
9 | fetchDepartment,
10 | selectProducts,
11 | } from "../../redux/productsSlice";
12 |
13 | const MainLayout = () => {
14 | const dispatch = useDispatch();
15 | const products = useSelector(selectProducts);
16 | useEffect(() => {
17 | dispatch(fetchDepartment());
18 | dispatch(fetchProducts());
19 | }, [dispatch]);
20 | return (
21 |
22 |
23 | {products ? (
24 |
25 |
26 |
27 |
28 | ) : (
29 |
30 | Loading...
31 |
32 | )}
33 |
34 | );
35 | };
36 |
37 | export default MainLayout;
38 |
--------------------------------------------------------------------------------
/src/mockup.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { db } from "./firebase.utils";
3 |
4 | // use each doc.id of firestore as its unique ID after fetching data from firebase
5 |
6 | const MOCKUP_DATA = [
7 | {
8 | departmentName: "Computers and Accessories",
9 | routeName: "computers-and-accessories",
10 | products: [
11 | {
12 | title: "New Apple iPad Pro (12.9-inch, Wi-Fi, 256GB) - Space Gray",
13 | price: 760.35,
14 | seller: "Amazon",
15 | brand: "Apple",
16 | rate: 5,
17 | count: 100,
18 | imgUrl:
19 | "https://m.media-amazon.com/images/I/81SGb5l+lZL._AC_UY218_.jpg",
20 | active: true,
21 | upcoming: false,
22 | missed: false,
23 | featured: true,
24 | gallery: [
25 | {
26 | original:
27 | "https://images-na.ssl-images-amazon.com/images/I/81SGb5l%2BlZL._AC_SL1500_.jpg",
28 | thumbnail:
29 | "https://images-na.ssl-images-amazon.com/images/I/41PhQzleq-L._AC_SR38,50_AA50_.jpg",
30 | },
31 | {
32 | original:
33 | "https://images-na.ssl-images-amazon.com/images/I/91zyHoys2jL._AC_SL1500_.jpg",
34 | thumbnail:
35 | "https://images-na.ssl-images-amazon.com/images/I/41Amb7qeEWL._AC_SR38,50_AA50_.jpg",
36 | },
37 | {
38 | original:
39 | "https://images-na.ssl-images-amazon.com/images/I/71oxyKvOTbL._AC_SL1500_.jpg",
40 | thumbnail:
41 | "https://images-na.ssl-images-amazon.com/images/I/41UqtjhGdLL._AC_SR38,50_AA50_.jpg",
42 | },
43 | {
44 | original:
45 | "https://images-na.ssl-images-amazon.com/images/I/81gyPqvQiDL._AC_SL1500_.jpg",
46 | thumbnail:
47 | "https://images-na.ssl-images-amazon.com/images/I/41Ybicv3XuL._AC_SR38,50_AA50_.jpg",
48 | },
49 | ],
50 | description:
51 | " 12.9-inch edge-to-edge Liquid Retina display with ProMotion, True Tone, and P3 wide color; A12Z Bionic chip with Neural Engine;12MP Wide camera, 10MP Ultra Wide camera, and LiDAR Scanner;7MP TrueDepth front camera;Face ID for secure authentication and Apple Pay;Four speaker audio and five studio- quality microphones; 802.11ax Wi- Fi 6;Up to 10 hours of battery life;USB - C connector for charging and accessories; Support for Magic Keyboard, Smart Keyboard Folio, and Apple Pencil",
52 | },
53 | {
54 | title:
55 | "Apple MacBook Air (13-inch Retina Display, 8GB RAM, 256GB SSD Storage)",
56 | price: 839.99,
57 | seller: "Amazon",
58 | brand: "Apple",
59 | rate: 4,
60 | count: 280,
61 | imgUrl:
62 | "https://m.media-amazon.com/images/I/71thf1SYnGL._AC_UY218_.jpg",
63 | active: false,
64 | upcoming: true,
65 | missed: false,
66 | featured: true,
67 | gallery: [
68 | {
69 | original:
70 | "https://images-na.ssl-images-amazon.com/images/I/71thf1SYnGL._AC_SL1500_.jpg",
71 | thumbnail:
72 | "https://images-na.ssl-images-amazon.com/images/I/41Kp8-ILkYL._AC_SR38,50_AA50_.jpg",
73 | },
74 | {
75 | original:
76 | "https://images-na.ssl-images-amazon.com/images/I/81e1YAr8YlL._AC_SL1500_.jpg",
77 | thumbnail:
78 | "https://images-na.ssl-images-amazon.com/images/I/41mb7IKbevL._AC_SR38,50_AA50_.jpg",
79 | },
80 | {
81 | original:
82 | "https://images-na.ssl-images-amazon.com/images/I/71p1u5qhgmL._AC_SL1500_.jpg",
83 | thumbnail:
84 | "https://images-na.ssl-images-amazon.com/images/I/31VGv0DOdbL._AC_SR38,50_AA50_.jpg",
85 | },
86 | ],
87 | description:
88 | " Stunning 13.3-inch Retina display with True Tone technology; Backlit Magic Keyboard and Touch ID; Tenth-generation Intel Core i3 processor; Intel Iris Plus Graphics; Fast SSD storage; 8GB of memory; Stereo speakers with wider stereo sound; Two Thunderbolt 3 (USB-C) ports; Up to 11 hours of battery life; Force Touch trackpad",
89 | },
90 | {
91 | title:
92 | "Apple MacBook Pro with Intel Processor (13-inch, 8GB RAM, 256GB SSD Storage) - Space Gray",
93 | price: 1109.99,
94 | seller: "Apple",
95 | brand: "Apple",
96 | rate: 4.5,
97 | count: 30,
98 | imgUrl:
99 | "https://m.media-amazon.com/images/I/71HI3U9ZwZL._AC_UY218_.jpg",
100 | active: true,
101 | upcoming: false,
102 | missed: false,
103 | featured: true,
104 | gallery: [
105 | {
106 | original:
107 | "https://images-na.ssl-images-amazon.com/images/I/71HI3U9ZwZL._AC_SL1500_.jpg",
108 | thumbnail:
109 | "https://images-na.ssl-images-amazon.com/images/I/41J5KJVM3FL._AC_SR38,50_AA50_.jpg",
110 | },
111 | {
112 | original:
113 | "https://images-na.ssl-images-amazon.com/images/I/71Wl6Bv6o0L._AC_SL1500_.jpg",
114 | thumbnail:
115 | "https://images-na.ssl-images-amazon.com/images/I/31os7oouNEL._AC_SR38,50_AA50_.jpg",
116 | },
117 | {
118 | original:
119 | "https://images-na.ssl-images-amazon.com/images/I/71LY-aeAEnL._AC_SL1500_.jpg",
120 | thumbnail:
121 | "https://images-na.ssl-images-amazon.com/images/I/31Xk3NpUCfL._AC_SR38,50_AA50_.jpg",
122 | },
123 | ],
124 | description:
125 | "Eighth-generation quad-core Intel Core i5 processor; Brilliant Retina display with True Tone technology; Backlit Magic Keyboard; Touch Bar and Touch ID; Intel Iris Plus Graphics 645; Ultrafast SSD; Two Thunderbolt 3 (USB-C) ports; Up to 10 hours of battery life; 802.11ac Wi-Fi; Force Touch trackpad",
126 | },
127 | {
128 | title:
129 | "Fire TV Stick 4K streaming device with Alexa Voice Remote | Dolby Vision | 2018 release",
130 | price: 29.99,
131 | seller: "Amazon",
132 | brand: "Amazon",
133 | rate: 3,
134 | count: 602,
135 | imgUrl:
136 | "https://m.media-amazon.com/images/I/51CgKGfMelL._AC_UY218_.jpg",
137 | active: false,
138 | upcoming: true,
139 | missed: false,
140 | featured: true,
141 | gallery: [
142 | {
143 | original:
144 | "https://images-na.ssl-images-amazon.com/images/I/61b-Vw7i5LL._AC_SL1000_.jpg",
145 | thumbnail:
146 | "https://images-na.ssl-images-amazon.com/images/I/51-LstfxkML._AC_US40_AA50_.jpg",
147 | },
148 | {
149 | original:
150 | "https://images-na.ssl-images-amazon.com/images/I/51ZdmnHKukL._AC_SL1000_.jpg",
151 | thumbnail:
152 | "https://images-na.ssl-images-amazon.com/images/I/41SR4yCI7aL._AC_US40_AA50_.jpg",
153 | },
154 | {
155 | original:
156 | "https://images-na.ssl-images-amazon.com/images/I/51FvcXWGgHL._AC_SL1000_.jpg",
157 | thumbnail:
158 | "https://images-na.ssl-images-amazon.com/images/I/417gof%2BW2cL._AC_US40_AA50_.jpg",
159 | },
160 | ],
161 | description:
162 | "A Certified Refurbished Fire TV Stick with all-new Alexa Voice Remote is refurbished, tested, and certified to look and work like new.; Fire TV Stick, the #1 best-selling streaming media player, with Alexa Voice Remote (2nd Gen). Use the dedicated power, volume, and mute buttons to control your TV, soundbar, and receiver.; Launch and control content with the Alexa Voice Remote. Watch favorites from Netflix, YouTube, Prime Video, STARZ, SHOWTIME, or CBS All Access, plus stream for free with Pluto TV, IMDb TV, and others.; Fire TV Stick devices have more storage for apps and games than any other streaming media stick.; Experience tens of thousands of channels, apps, and Alexa skills, plus browse millions of websites like Facebook and Reddit using Firefox or Silk.; Alexa on Fire TV provides the most comprehensive voice experience of any streaming media player—view live camera feeds, check the weather, dim the lights, and stream music.; Amazon Prime members get unlimited access to thousands of movies and TV episodes, plus ad-free listening to millions of songs with Prime Music.",
163 | },
164 | {
165 | title:
166 | "HP 15-dy1036nr 10th Gen Intel Core i5-1035G1, 15-Inch FHD Laptop, Natural Silver",
167 | price: 889.99,
168 | seller: "Amazon",
169 | brand: "HP",
170 | rate: 3.5,
171 | count: 69,
172 | imgUrl:
173 | "https://m.media-amazon.com/images/I/71WSt8D7KFL._AC_UY218_.jpg",
174 | active: false,
175 | upcoming: true,
176 | missed: false,
177 | featured: true,
178 | gallery: [
179 | {
180 | original:
181 | "https://images-na.ssl-images-amazon.com/images/I/71WSt8D7KFL._AC_SX569_.jpg",
182 | thumbnail:
183 | "https://images-na.ssl-images-amazon.com/images/I/41RZjMPGN0L._AC_US40_.jpg",
184 | },
185 | {
186 | original:
187 | "https://images-na.ssl-images-amazon.com/images/I/81rSbRZ1l0L._AC_SX569_.jpg",
188 | thumbnail:
189 | "https://images-na.ssl-images-amazon.com/images/I/51GMRgAxZgL._AC_US40_.jpg",
190 | },
191 | {
192 | original:
193 | "https://images-na.ssl-images-amazon.com/images/I/71hStpySRmL._AC_SX569_.jpg",
194 | thumbnail:
195 | "https://images-na.ssl-images-amazon.com/images/I/41HyPHy4hML._AC_US40_.jpg",
196 | },
197 | ],
198 | description:
199 | "10th Gen Intel Core i7-10510U (1.8 GHz base frequency, up to 4.9 GHz base with Intel Turbo Boost Technology, 8 MB cache, 4 cores) ; diagonal HD SVA BrightView micro-edge WLED-backlit (1366 x 768) Display; Intel UHD Graphics; 8GB DDR4-2666MHz SDRAM Memory for full-power multitasking; 128GB SSD; Ideal for Home, Student, Professionals, Small Business, School Education, and Commercial Enterprise, Online Class, Google Classroom, Remote Learning, Zoom Ready.; 802.11b/g/n (1x1) Wi-Fi and Bluetooth 4.2 combo; 1 USB 3.1 Gen 1 Type-C (Data Transfer Only, 5 Gb/s signaling rate); 2 USB 3.1 Gen 1 Type-A (Data Transfer Only); 1 RJ-45; 1 AC smart pin; 1 HDMI 1.4b; 1 headphone/microphone combo; 1 multi-format SD media card reader; ▌Authorized BROAGE Bundle ▌Bundled with BROAGE MousePad, Authorized Sellers ONLY. Windows 10 Home 64-Bit, Natural silver",
200 | },
201 | ],
202 | },
203 | {
204 | departmentName: "Camera & Photo",
205 | routeName: "camera-photo",
206 | products: [
207 | {
208 | title:
209 | "Panasonic LUMIX DC-FZ80GN-K 4K Bridge Hi-Zoom Point and Shoot Travel Camera, Black",
210 | price: 455.0,
211 | seller: "Amazon",
212 | brand: "Panasonic",
213 | rate: 5,
214 | count: 11,
215 | imgUrl:
216 | "https://images-fe.ssl-images-amazon.com/images/I/41jwbCD0khL._AC_AA160_.jpg",
217 | active: false,
218 | upcoming: false,
219 | missed: true,
220 | featured: true,
221 | gallery: [
222 | {
223 | original:
224 | "https://images-na.ssl-images-amazon.com/images/I/917LEZ%2Bit3L._AC_SL1500_.jpg",
225 | thumbnail:
226 | "https://images-na.ssl-images-amazon.com/images/I/51VWc0XqprL._AC_US40_AA50_.jpg",
227 | },
228 | {
229 | original:
230 | "https://images-na.ssl-images-amazon.com/images/I/61D1PKdrRqL._AC_.jpg",
231 | thumbnail:
232 | "https://images-na.ssl-images-amazon.com/images/I/61D1PKdrRqL._AC_US40_AA50_.jpg",
233 | },
234 | {
235 | original:
236 | "https://images-na.ssl-images-amazon.com/images/I/6150C4%2BFrmL._AC_.jpg",
237 | thumbnail:
238 | "https://images-na.ssl-images-amazon.com/images/I/6150C4%2BFrmL._AC_US40_AA50_.jpg",
239 | },
240 | ],
241 | description:
242 | "Superb DSLM image quality without the bulk and weight of traditional DSLRs; Never miss a photo with three unique 4K ultra HD video pause and save 4K photo modes; Fast and precise auto focusing tracks the subject; Focus mode AFS (single) / AFF (flexible) / AFC (continuous) / MF, AF mode face/eye detection / tracking / 49 area / custom Multi / 1 area / pinpoint; Class leading, ultra compact, interchangeable lens and accessory option; HDMI: microHDMI TypeD / VIERA Link,video: Auto / 4K / 1080p / 1080i / 720p / 480p, Audio: stereo; Unwire your creativity with integrated Wi Fi sharing",
243 | },
244 | {
245 | title:
246 | "Nikon D3500 KIT + AF-P 18-55 VR + AF-P 70-300 VR Twin Lens Kit, Black",
247 | price: 898.2,
248 | seller: "Nikon",
249 | brand: "Nikon",
250 | rate: 4,
251 | count: 27,
252 | imgUrl:
253 | "https://images-fe.ssl-images-amazon.com/images/I/413eVsQoYeL._AC_AA160_.jpg",
254 | active: true,
255 | upcoming: false,
256 | missed: false,
257 | featured: true,
258 | gallery: [
259 | {
260 | original:
261 | "https://images-na.ssl-images-amazon.com/images/I/61A4tg5IFhL._AC_SL1000_.jpg",
262 | thumbnail:
263 | "https://images-na.ssl-images-amazon.com/images/I/51RHexdJkUL._AC_US40_AA50_.jpg",
264 | },
265 | {
266 | original:
267 | "https://images-na.ssl-images-amazon.com/images/I/61MtcYSMoLL._AC_SL1000_.jpg",
268 | thumbnail:
269 | "https://images-na.ssl-images-amazon.com/images/I/51KNi4NJLzL._AC_US40_AA50_.jpg",
270 | },
271 | {
272 | original:
273 | "https://images-na.ssl-images-amazon.com/images/I/61a1bF0a3rL._AC_SL1000_.jpg",
274 | thumbnail:
275 | "https://images-na.ssl-images-amazon.com/images/I/51x4UIVB2gL._AC_US40_AA50_.jpg",
276 | },
277 | ],
278 | description:
279 | "This Grace Photo Camera Bundle Comes Complete With Manufacturer Supplied Accessories(U.S. Compatible) and a 1 Year Seller Provided Warranty; Package Includes: Nikon D3500 DSLR Camera (International Version) 24.2MP DX-Format CMOS Sensor; EXPEED 4 Image Processor; No Optical Low-Pass Filter; Native ISO 100-25600; 5 fps Shooting; 3.0 921k-Dot LCD Monitor; Full HD 1080p Video Recording at 60 fps; Multi-CAM 1000 11-Point AF System; SnapBridge Bluetooth Connectivity Nikon AF-P DX NIKKOR 18-55mm f/3.5-5.6G VR Lens - F-Mount Lens/DX Format , 27-82.5mm (35mm Equivalent) , Super Integrated Coating , Pulse Stepping Motor AF System , Nikon VR Image Stabilization , Access Lens Settings in Camera Menu , Ro",
280 | },
281 | {
282 | title:
283 | "G7X Mark II + Selphy Portable Printer + 40 Sheets of Photo Paper",
284 | price: 1064.8,
285 | seller: "Canon",
286 | brand: "Canon",
287 | rate: 4.5,
288 | count: 3,
289 | imgUrl:
290 | "https://images-fe.ssl-images-amazon.com/images/I/41KLEZXgg0L._AC_AA160_.jpg",
291 | active: true,
292 | upcoming: false,
293 | missed: false,
294 | featured: true,
295 | gallery: [
296 | {
297 | original:
298 | "https://images-na.ssl-images-amazon.com/images/I/416b8xNfDJL._AC_.jpg",
299 | thumbnail:
300 | "https://images-na.ssl-images-amazon.com/images/I/416b8xNfDJL._AC_US40_AA50_.jpg",
301 | },
302 | {
303 | original:
304 | "https://images-na.ssl-images-amazon.com/images/I/61Srr4ACqJL._AC_SL1500_.jpg",
305 | thumbnail:
306 | "https://images-na.ssl-images-amazon.com/images/I/416IPK%2BEVDL._AC_US40_AA50_.jpg",
307 | },
308 | {
309 | original:
310 | "https://images-na.ssl-images-amazon.com/images/I/71ef4iykr5L._AC_SL1500_.jpg",
311 | thumbnail:
312 | "https://images-na.ssl-images-amazon.com/images/I/41vjntfdIcL._AC_US40_AA50_.jpg",
313 | },
314 | ],
315 | description:
316 | "The advanced video capabilities of the Power Shot G7 X Mark II camera can capture moments in the quality they deserve ; Features a large 1.0 inch, 20.1 Megapixel CMOS sensor that helps capture high quality images and videos with a wide dynamic range. Autofocus system features ttl autofocus and manual focus. Operating temperature is 0 to 40 degree celsius. Note charging time varies considerably depending on the remaining battery power; An aperture value of f/1.8 at the wide angle and f/2.8 when fully zoomed to a factor of 4.2x (24 100 millimeter), this lens equipped to capture a variety of situations with precision ; High resolution, 3.0 inches LCD monitor that tilts up 180 degrees and down 45 degrees is ideal for self portraits and capturing pictures at high and low angles with ease Built in Wi Fi for on the go convenience and the ability to easily post your images to select social networking and media sites ; With the ability to shoot continuously at up to 8 fps, it can help you capture incredible images with nearly no time wasted",
317 | },
318 | {
319 | title: "Ricoh Theta V 360 Spherical Camera",
320 | price: 599.99,
321 | seller: "Amazon",
322 | brand: "Ricoh",
323 | rate: 3.5,
324 | count: 110,
325 | imgUrl:
326 | "https://images-fe.ssl-images-amazon.com/images/I/31jNXNVKclL._AC_AA160_.jpg",
327 | active: false,
328 | upcoming: true,
329 | missed: false,
330 | featured: true,
331 | gallery: [
332 | {
333 | original:
334 | "https://images-na.ssl-images-amazon.com/images/I/61%2BGE4CgqrL._AC_SL1231_.jpg",
335 | thumbnail:
336 | "https://images-na.ssl-images-amazon.com/images/I/41ficUhE82L._AC_US40_AA50_.jpg",
337 | },
338 | {
339 | original:
340 | "https://images-na.ssl-images-amazon.com/images/I/71Y3gcvXl%2BL._AC_SL1000_.jpg",
341 | thumbnail:
342 | "https://images-na.ssl-images-amazon.com/images/I/61oTk5H9apL._AC_US40_AA50_.jpg",
343 | },
344 | {
345 | original:
346 | "https://images-na.ssl-images-amazon.com/images/I/61lUvafK6IL._AC_SL1000_.jpg",
347 | thumbnail:
348 | "https://images-na.ssl-images-amazon.com/images/I/51OWRVBrZIL._AC_US40_AA50_.jpg",
349 | },
350 | ],
351 | description:
352 | "Theta V shoots hi-def smooth 360 Degree video at 30 fps @ 3840 x 1920 pixels or 4K. It supports the H.264 file format for video recording ; Remote release: CA-3-compatible ; Hi-res 360 Degree spherical stills & video with improvements to the image quality. New Qualcomm Snapdragon processor has completely enhanced the exposure accuracy and white balance algorithm. Object distance- Approx. 10cm - ∞ (from front of lens).Lens configuration:7 elements in 6 groups Theta has a 4-channel microphone that supports 360 Degree spatial audio recording built in Omnidirectional audio is recorded not just in the horizontal direction but also in the vertical direction. Storage temperature range - -20°C - 60°C The world’s-first Remote Playback capable fully spherical camera allows users to wirelessly playback 360 Degree images and videos on a large-screen display Using a compatible wireless display adapter ; Wireless LAN transfer speed (WLAN, MAX): 2.4GHz, 3mm: 20Mbps; 2.4GHz, 10mm: 10Mbps; 5GHz, 3mm: 50Mbps; 5GHz, 10mm: 10Mbps. The camera can always be connected to a smartphone using Bluetooth low energy (BLE) This function provides improvements in usability and power consumption",
353 | },
354 | {
355 | title:
356 | "Olympus OM-D E-M5 Mark III Camera - Kit with 14-150mm Lens (Silver)",
357 | price: 1895.0,
358 | seller: "Olympus",
359 | brand: "Olympus",
360 | rate: 3,
361 | count: 7,
362 | imgUrl:
363 | "https://m.media-amazon.com/images/I/81mSUtJFdZL._AC_UL320_.jpg",
364 | active: true,
365 | upcoming: false,
366 | missed: false,
367 | featured: true,
368 | gallery: [
369 | {
370 | original:
371 | "https://images-na.ssl-images-amazon.com/images/I/71jT8sUbriL._AC_SL1500_.jpg",
372 | thumbnail:
373 | "https://images-na.ssl-images-amazon.com/images/I/41j9B76ly6L._AC_US40_.jpg",
374 | },
375 | {
376 | original:
377 | "https://images-na.ssl-images-amazon.com/images/I/81xViZ0Gm1L._AC_SL1500_.jpg",
378 | thumbnail:
379 | "https://images-na.ssl-images-amazon.com/images/I/41FoCm5aq-L._AC_US40_.jpg",
380 | },
381 | {
382 | original:
383 | "https://images-na.ssl-images-amazon.com/images/I/71BVeMB9ZqL._AC_SL1500_.jpg",
384 | thumbnail:
385 | "https://images-na.ssl-images-amazon.com/images/I/41zrlbdP%2B4L._AC_US40_.jpg",
386 | },
387 | ],
388 | description:
389 | "20MP live MOS sensor ; Portable, weather sealed design; 121-point all-cross-type on-chip phase detection AF; Compact, in-body 5-axis image stabilization (up to approx. 5.5 EV steps of compensation); 50MP tripod high-res shot",
390 | },
391 | ],
392 | },
393 | {
394 | departmentName: "Mobile Phones & Communication",
395 | routeName: "mobile-phones-communication",
396 | products: [
397 | {
398 | title:
399 | "Wireless Headphones Bluetooth Earbuds with Charging Case Noise Cancelling 3D Stereo Headphones",
400 | price: 220.0,
401 | seller: "Apple",
402 | brand: "Apple",
403 | rate: 5,
404 | count: 110,
405 | imgUrl:
406 | "https://m.media-amazon.com/images/I/51uxlKNiGAL._AC_UY218_.jpg",
407 | active: true,
408 | upcoming: false,
409 | missed: false,
410 | featured: true,
411 | gallery: [
412 | {
413 | original:
414 | "https://images-na.ssl-images-amazon.com/images/I/71FlmfMetKL._AC_SX522_.jpg",
415 | thumbnail:
416 | "https://images-na.ssl-images-amazon.com/images/I/51vDudmSP5L._AC_US40_.jpg",
417 | },
418 | {
419 | original:
420 | "https://images-na.ssl-images-amazon.com/images/I/61M5T%2BhSUpL._AC_SX522_.jpg",
421 | thumbnail:
422 | "https://images-na.ssl-images-amazon.com/images/I/41C5XBgAuPL._AC_US40_.jpg",
423 | },
424 | {
425 | original:
426 | "https://images-na.ssl-images-amazon.com/images/I/610E2mym0zL._AC_SX522_.jpg",
427 | thumbnail:
428 | "https://images-na.ssl-images-amazon.com/images/I/51Z4fUS0%2BIL._AC_US40_.jpg",
429 | },
430 | ],
431 | description:
432 | "♫【Bluetooth upgrade 5.0 technology】:Wireless earbuds adopt professional chipset and equipped with better than ordinary 5.0 chip to ensure excellent audio performance with fast, stable and efficient transmission.Offers a truly natural, authentic sound and powerful bass performance with 14mm large size speaker driver.; ♫【Dual HD microphones】:Built-in microphones support phone calls and voice assistant with either or both earbuds. With brilliant acoustic design, earphones deliver a clean response with no distortions.CVC8.0 noise cancelling technology which can filters out external noise & enhances the sound of your voice for clear hands-free calls, allow you hear in louder environments.;; ♫【Quality Battery Life 24 Hours】:Support fast charging, charging for 15 minutes, using for two hours.The charging case has 4 charging contacts for safer charging. The earplugs can be used for 5-6 hours in just 1 hour of charging time. The 650mAh portable magnetic charging case can charge the headset 4 times and extend the playback time for up to 20 hours.; ♫【Comfortable & Lightweight Design】:Get rid of the shackles and the real liberation. After layer testing, After testing, we chose an ergonomically designed headset (65°), comfortable to wear, not easy to drop, and the headphones are very lightweight (3.5g) to ensure ear stability and comfort.",
433 | },
434 | {
435 | title: "Sony Noise Cancelling Headphones WH1000XM3",
436 | price: 324.8,
437 | seller: "Sony",
438 | brand: "Sony",
439 | rate: 4.5,
440 | count: 507,
441 | imgUrl:
442 | "https://m.media-amazon.com/images/I/71IIenbgEcL._AC_UY218_.jpg",
443 | active: true,
444 | upcoming: false,
445 | missed: false,
446 | featured: true,
447 | gallery: [
448 | {
449 | original:
450 | "https://images-na.ssl-images-amazon.com/images/I/71IIenbgEcL._AC_SX522_.jpg",
451 | thumbnail:
452 | "https://images-na.ssl-images-amazon.com/images/I/41xa1cPWH3L._AC_US40_.jpg",
453 | },
454 | {
455 | original:
456 | "https://images-na.ssl-images-amazon.com/images/I/518ISPl1BZL._AC_SX522_.jpg",
457 | thumbnail:
458 | "https://images-na.ssl-images-amazon.com/images/I/31Y8K%2ByUXkL._AC_US40_.jpg",
459 | },
460 | {
461 | original:
462 | "https://images-na.ssl-images-amazon.com/images/I/51CjGcEXyIL._AC_SX522_.jpg",
463 | thumbnail:
464 | "https://images-na.ssl-images-amazon.com/images/I/41n3w7DNInL._AC_US40_.jpg",
465 | },
466 | ],
467 | description:
468 | "BUNDLE INCLUDES: Sony WH-1000XM3 Wireless Noise-Canceling Over-Ear Headphones (Black) with Sony SRS-XB10 Portable Wireless Bluetooth Speaker ; VIRTUAL SOUND: Smart Listening by Adaptive Sound Control / SENSE ENGINE for virtual surround sound; POWERFUL DRIVERS: Hi-Res audio LCP drivers for up to 40 kHz frequency; CONNECTIVITY: Bluetooth 4.1 LDAC connection / Connect to the Sony Headphones Connect App; SONY AUTHORIZED DEALER: Includes USA Manufacturer Guarantee",
469 | },
470 | {
471 | title:
472 | "Nokia 5.3 Android One Smartphone (Official Australian Version 2020)",
473 | price: 364.8,
474 | seller: "Nokia",
475 | brand: "Nokia",
476 | rate: 4,
477 | count: 30,
478 | imgUrl:
479 | "https://m.media-amazon.com/images/I/71M8ocFNBgL._AC_UL320_.jpg",
480 | active: true,
481 | upcoming: false,
482 | missed: false,
483 | featured: true,
484 | gallery: [
485 | {
486 | original:
487 | "https://images-na.ssl-images-amazon.com/images/I/71xFT1Yi11L._AC_SX425_.jpg",
488 | thumbnail:
489 | "https://images-na.ssl-images-amazon.com/images/I/61A1XGiyH1L._AC_US40_.jpg",
490 | },
491 | {
492 | original:
493 | "https://images-na.ssl-images-amazon.com/images/I/61HxZaQlYQL._AC_SX425_.jpg",
494 | thumbnail:
495 | "https://images-na.ssl-images-amazon.com/images/I/4102YfWF8LL._AC_US40_.jpg",
496 | },
497 | {
498 | original:
499 | "https://images-na.ssl-images-amazon.com/images/I/31CGbfSTdeL._AC_SX425_.jpg",
500 | thumbnail:
501 | "https://images-na.ssl-images-amazon.com/images/I/31CGbfSTdeL._AC_US40_.jpg",
502 | },
503 | ],
504 | description:
505 | "Go big with the Nokia 5.3 smartphone's epic-sized 6. 55-inch HD+ display and incredible quad rear camera that captures stunning pictures even in low light, helps you select your best shot, and creates professional-looking portraits.; Capture great detail with the quad camera's 13 MP main lens, get everything in the frame with the ultra-wide, 118-degree 5 MP lens, focus in on the tiniest details with the 2 MP macro lens, and create blurred bokeh images with the 2 MP depth sensor.; Stay in the game for longer thanks to the dynamic Qualcomm Snapdragon 665 processor's silky smooth graphics and AI-assisted Adaptive Battery that saves energy for the apps you use the most and stays charged for up to 2 days.; The Nokia 5.3 and Google Assistant utilize AI-powered machine learning to learn and adapt to you with every use, and the dedicated Google Assistant button makes it easy to ask questions, update calendars, check traffic, and more.",
506 | },
507 | {
508 | title:
509 | "Samsung Galaxy A11 Smartphone with 2 Year Manufacturer Warranty,Black",
510 | price: 233.89,
511 | seller: "Amazon",
512 | brand: "Samsung",
513 | rate: 3,
514 | count: 20,
515 | imgUrl:
516 | "https://m.media-amazon.com/images/I/61aoEfQb63L._AC_UL320_.jpg",
517 | active: true,
518 | upcoming: false,
519 | missed: false,
520 | featured: true,
521 | gallery: [
522 | {
523 | original:
524 | "https://images-na.ssl-images-amazon.com/images/I/81qOdpVypzL._AC_SY550_.jpg",
525 | thumbnail:
526 | "https://images-na.ssl-images-amazon.com/images/I/4187bqrxiTL._AC_SR38,50_.jpg",
527 | },
528 | {
529 | original:
530 | "https://images-na.ssl-images-amazon.com/images/I/817ADBC6U%2BL._AC_SX466_.jpg",
531 | thumbnail:
532 | "https://images-na.ssl-images-amazon.com/images/I/41xWhSJaPQL._AC_SR38,50_.jpg",
533 | },
534 | {
535 | original:
536 | "https://images-na.ssl-images-amazon.com/images/I/712HPEtUe8L._AC_SY550_.jpg",
537 | thumbnail:
538 | "https://images-na.ssl-images-amazon.com/images/I/21z6rj-fCEL._AC_SR38,50_.jpg",
539 | },
540 | ],
541 | description:
542 | "CHARGE UP. POWER THROUGH: You need a phone you can rely on. Thanks to the long-lasting battery(Based on average battery life under typical usage conditions. Average expected performance based on typical use. Actual battery life depends on factors such as network, features selected, frequency of calls, and voice, data, and other application usage patterns. Results may vary.) of the Galaxy A11, your phone will have enough power to do almost anything. You can spend more time scrolling, texting and sharing, and less time looking for an outlet to recharge.; TAKE YOUR ENTERTAINMENT TO THE EDGE: A 6.4” Infinity-O Display Measured diagonally, the screen size is 6.4;with accounting for the rounded corners. Actual viewable area is less due to the rounded corners and camera lens. gives you plenty of room to scroll through your feeds, see the latest news and keep up with friends and family. Colors and details look bright and bold on its HD+ screen.; THREE LENSES. ENDLESS POTENTIAL: Getting a great photo and video is now easier with this versatile camera. In addition to portraits and videos, the A11's depth lens helps you take beautiful portraits too.; STORE MORE: Whether you’re snapping, storing or downloading, this device can keep it all. If the built-in 32GB of memory enough, you can expand the base storage up to an amazing 512GB with a microSD card ",
543 | },
544 | {
545 | title:
546 | "Apple iPhone 11 White 128GB Physical Dual Sim [AU Seller] MWN82CH/A",
547 | price: 1299.0,
548 | seller: "Apple",
549 | brand: "Apple",
550 | rate: 5,
551 | count: 10,
552 | imgUrl:
553 | "https://m.media-amazon.com/images/I/61U+Xz90BAL._AC_UL320_.jpg",
554 | active: true,
555 | upcoming: false,
556 | missed: false,
557 | featured: true,
558 | gallery: [
559 | {
560 | original:
561 | "https://m.media-amazon.com/images/I/81j2j9x-KWL._FMwebp__.jpg",
562 | thumbnail:
563 | "https://m.media-amazon.com/images/I/41YQjtzk7UL._SS40_FMwebp__.jpg",
564 | },
565 | {
566 | original:
567 | "https://m.media-amazon.com/images/I/71dcUoPEBsL._FMwebp__.jpg",
568 | thumbnail:
569 | "https://m.media-amazon.com/images/I/31P1p9qLXcL._SS40_FMwebp__.jpg",
570 | },
571 | {
572 | original:
573 | "https://m.media-amazon.com/images/I/815peUj+oYL._FMwebp__.jpg",
574 | thumbnail:
575 | "https://m.media-amazon.com/images/I/41AisChzPDL._SS40_FMwebp__.jpg",
576 | },
577 | ],
578 | description:
579 | "OFFER INCLUDES: An Apple iPhone and a wireless plan with unlimited data/talk/text; WIRELESS PLAN: Unlimited talk, text, and data with mobile hotspot, nationwide coverage, and international reach. No long-term contract required.; PROGRAM DETAILS: When you add this offer to cart, it will reflect 3 items: the iPhone, SIM kit, and carrier subscription; 6.5-inch Super Retina XDR OLED display, water and dust resistant, with Face ID; Triple-camera system with 12MP Ultra Wide camera, 12MP TrueDepth front camera with Portrait mode. Fast-charge and wireless charging capable; Note: The box label simply says “iPhone” but the phone is the iPhone 11 Pro Max",
580 | },
581 | ],
582 | },
583 | {
584 | departmentName: "Clothing, Shoes & Accessories",
585 | routeName: "clothing-shoes-accessories",
586 | products: [
587 | {
588 | title: "Mens 21861 Extreme Motion Swope Cargo Short Cargo",
589 | price: 89.99,
590 | seller: "Perfect Clothing",
591 | brand: "Lee",
592 | rate: 5,
593 | count: 1038,
594 | imgUrl:
595 | "https://m.media-amazon.com/images/I/81RHhaW4SOL._AC_UL320_.jpg",
596 | active: true,
597 | upcoming: false,
598 | missed: false,
599 | featured: true,
600 | gallery: [
601 | {
602 | original:
603 | "https://images-na.ssl-images-amazon.com/images/I/81RHhaW4SOL._AC_UY500_.jpg",
604 | thumbnail:
605 | "https://images-na.ssl-images-amazon.com/images/I/415WG5oPk9L._AC_SR38,50_.jpg",
606 | },
607 | {
608 | original:
609 | "https://images-na.ssl-images-amazon.com/images/I/81XnXFCp8pL._AC_UY500_.jpg",
610 | thumbnail:
611 | "https://images-na.ssl-images-amazon.com/images/I/41XtmnSUy0L._AC_SR38,50_.jpg",
612 | },
613 | {
614 | original:
615 | "https://images-na.ssl-images-amazon.com/images/I/A1nAhaaj5KL._AC_UX466_.jpg",
616 | thumbnail:
617 | "https://images-na.ssl-images-amazon.com/images/I/51gSOYk2ptL._AC_SR38,50_.jpg",
618 | },
619 | ],
620 | description:
621 | "98% Cotton, 2% Spandex; Imported; Zipper closure; Machine Wash; Relaxed fit; hits at the knee; Featuring an elastic waistband that moves with you; Side cargo pockets and cell phone pocket",
622 | },
623 | {
624 | title: "Authentic Originals Sueded Fleece Pullover",
625 | price: 68.8,
626 | seller: "Amazon",
627 | brand: "Champion",
628 | rate: 3.5,
629 | count: 9,
630 | imgUrl:
631 | "https://m.media-amazon.com/images/I/91Sx6JsV9CL._AC_UL320_.jpg",
632 | active: true,
633 | upcoming: false,
634 | missed: false,
635 | featured: true,
636 | gallery: [
637 | {
638 | original:
639 | "https://images-na.ssl-images-amazon.com/images/I/91Sx6JsV9CL._AC_UX425_.jpg",
640 | thumbnail:
641 | "https://images-na.ssl-images-amazon.com/images/I/41uOGZMbBGL._AC_SR38,50_.jpg",
642 | },
643 | {
644 | original:
645 | "https://m.media-amazon.com/images/I/A1nqkyzZIPL._AC_SX425._SX._UX._SY._UY_.jpg",
646 | thumbnail:
647 | "https://m.media-amazon.com/images/I/51YC-ctGalL._AC_SR38,50_.jpg",
648 | },
649 | ],
650 | description:
651 | "80% Cotton, 20% Polyester; Imported; Machine Wash; Fleece is sueded outside, incredibly soft inside; Hood has contrasting jersey lining; Flat woven draw cord with turned and tacked ends; Ribbed cuffs and waistband; Forward shoulders; relaxed waistband; pouch pocket",
652 | },
653 | {
654 | title: "Half Dome Hoodie, TNF",
655 | price: 39.8,
656 | seller: "Amazon",
657 | brand: "The North Face",
658 | rate: 4.5,
659 | count: 1020,
660 | imgUrl:
661 | "https://m.media-amazon.com/images/I/81blGg02EJL._AC_UL320_.jpg",
662 | active: true,
663 | upcoming: false,
664 | missed: false,
665 | featured: true,
666 | gallery: [
667 | {
668 | original:
669 | "https://m.media-amazon.com/images/I/81xUYaah6pL._AC_SX385._SX._UX._SY._UY_.jpg",
670 | thumbnail:
671 | "https://m.media-amazon.com/images/I/418Pf6T0yOL._AC_SR38,50_.jpg",
672 | },
673 | {
674 | original:
675 | "https://m.media-amazon.com/images/I/815Bd5SgD2L._AC_SX385._SX._UX._SY._UY_.jpg",
676 | thumbnail:
677 | "https://m.media-amazon.com/images/I/31CIoFe3lRL._AC_SR38,50_.jpg",
678 | },
679 | {
680 | original:
681 | "https://m.media-amazon.com/images/I/81IPGpibsbL._AC_SX385._SX._UX._SY._UY_.jpg",
682 | thumbnail:
683 | "https://m.media-amazon.com/images/I/31gd9FQHmlL._AC_SR38,50_.jpg",
684 | },
685 | ],
686 | description:
687 | "SIGNATURE HOODIE. This standard fit sweatshirt is made with a recycled fabric blend. With enough room for layering, this hoodie is great for getting outside and enjoying cooler weather.; HOODED COVERAGE. An adjustable drawstring hood helps keep you covered and bundled up as the temperature cools off.;; CLASSIC STAPLE. For an extra layer to keep the chill away, turn to your favorite hoodie. Rib cuffs and hem enhance durability for lasting wear.; FRONT-POCKET STORAGE. A kangaroo pocket hold small items and help keep hands warm.; THE NORTH FACE. The North Face screen print adds a signature look to your sweatshirt",
688 | },
689 | {
690 | title: "Modern Series Extreme Motion Straight Fit Tapered Leg Jean",
691 | price: 28.09,
692 | seller: "Lee",
693 | brand: "Lee",
694 | rate: 4.5,
695 | count: 3441,
696 | imgUrl:
697 | "https://m.media-amazon.com/images/I/71ZsLtQOADL._AC_UL320_.jpg",
698 | active: true,
699 | upcoming: false,
700 | missed: false,
701 | featured: true,
702 | gallery: [
703 | {
704 | original:
705 | "https://images-na.ssl-images-amazon.com/images/I/71ZsLtQOADL._AC_UX466_.jpg",
706 | thumbnail:
707 | "https://images-na.ssl-images-amazon.com/images/I/31CKI8sFYzL._AC_SR38,50_.jpg",
708 | },
709 | {
710 | original:
711 | "https://images-na.ssl-images-amazon.com/images/I/71tMzRETIBL._AC_UX466_.jpg",
712 | thumbnail:
713 | "https://images-na.ssl-images-amazon.com/images/I/41cPHoa2%2BHL._AC_SR38,50_.jpg",
714 | },
715 | {
716 | original:
717 | "https://images-na.ssl-images-amazon.com/images/I/61HW9n3VP7L._AC_UY500_.jpg",
718 | thumbnail:
719 | "https://images-na.ssl-images-amazon.com/images/I/51-xJ00LMdL._AC_SR38,50_.jpg",
720 | },
721 | ],
722 | description:
723 | "69% Cotton, 20% Polyester, 8% Rayon, 3% Spandex; Imported; Button closure; Machine Wash; STRAIGHT FIT.With a straight fit through the hip and thigh, these jeans sit just below the waist with an Extreme Flex waistband for natural comfort.; CLASSIC 5-POCKET STYLING.A classic fit jean designed with an authentic five- pocket style.Our bestselling straight fit jean is designed with a timeless five- pocket style, perfect for most occasions.; JEANS FOR ALL. This jean is available in a midweight denim with a wide variety of washes, finishes, and fits. Add a classic denim jean to your everyday rotation or try out a new alternative wash -- there's a pair of Lee jeans to fit every style",
724 | },
725 | {
726 | title: "Graphic Powerblend Fleece Crew",
727 | price: 35.0,
728 | seller: "Champion",
729 | brand: "Champion",
730 | rate: 4.5,
731 | count: 4848,
732 | imgUrl:
733 | "https://m.media-amazon.com/images/I/91MPi4sVKLL._AC_UL320_.jpg",
734 | active: true,
735 | upcoming: false,
736 | missed: false,
737 | featured: true,
738 | gallery: [
739 | {
740 | original:
741 | "https://m.media-amazon.com/images/I/91MPi4sVKLL._AC_SY500._SX._UX._SY._UY_.jpg",
742 | thumbnail:
743 | "https://m.media-amazon.com/images/I/41-htzTiXoL._AC_SR38,50_.jpg",
744 | },
745 | {
746 | original:
747 | "https://m.media-amazon.com/images/I/9158gOHEJRL._AC_SY500._SX._UX._SY._UY_.jpg",
748 | thumbnail:
749 | "https://m.media-amazon.com/images/I/41c6wQaWm+L._AC_SR38,50_.jpg",
750 | },
751 | ],
752 | description:
753 | "50% Cotton, 50% Polyester; Imported; Pull On closure; Machine Wash; Better fit. Wider rib cuffs and hem; Soft comfortable fabric. A percentage of recycled fibers used in making the fabric",
754 | },
755 | ],
756 | },
757 | ];
758 |
759 | const Mockup = () => {
760 | const mockup = () => {
761 | MOCKUP_DATA.forEach((el) => {
762 | db.doc(`amazon/${el.routeName}`)
763 | .set({
764 | departmentName: el.departmentName,
765 | routeName: el.routeName,
766 | })
767 | .then(() => {
768 | el.products.forEach((product) => {
769 | db.collection("amazon")
770 | .doc(el.routeName)
771 | .collection("products")
772 | .add(product);
773 | });
774 | });
775 | });
776 | };
777 |
778 | return (
779 |
780 |
781 |
782 | );
783 | };
784 |
785 | export default Mockup;
786 |
--------------------------------------------------------------------------------
/src/redux/cartSelector.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from "@reduxjs/toolkit";
2 |
3 | const selectCart = state => state.cart.cart
4 | const selectTaxPercent = (state) => state.cart.taxPercent;
5 |
6 | export const selectCartItems = createSelector(
7 | selectCart,
8 | items => {
9 | return items
10 | }
11 | );
12 |
13 | export const selectCartItemsCount = createSelector(
14 | selectCartItems,
15 | cartItems => {
16 | if (cartItems)
17 | {
18 | return cartItems.reduce((acc, item) => acc + item.quantity, 0)
19 | }
20 | return 0
21 | }
22 | )
23 |
24 | export const selectSubtotal = createSelector(
25 | selectCartItems,
26 | cartItems => cartItems.reduce((acc, item) => acc + item.quantity * item.price,0 )
27 | )
28 |
29 | export const selectTax = createSelector(
30 | selectSubtotal,
31 | selectTaxPercent,
32 | (subtotal, taxPercent) => subtotal * (taxPercent/100)
33 | )
34 |
35 | export const selectTotal = createSelector(
36 | selectSubtotal,
37 | selectTax,
38 | (subtotal, tax) => subtotal + tax
39 | )
40 |
41 |
--------------------------------------------------------------------------------
/src/redux/cartSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | export const cartSlice = createSlice({
4 | name: "cart",
5 | initialState: {
6 | cart: [],
7 | taxPercent: 15,
8 | },
9 | reducers: {
10 | addItem: {
11 | reducer(state, action) {
12 | const existItemIndex = state.cart.findIndex(
13 | (item) => item.productId === action.payload.productId
14 | );
15 |
16 | //could use (JSON.stringify(existItem, null, 2)) to console.log result of state.cart.find
17 |
18 | if (existItemIndex >= 0) {
19 | state.cart[existItemIndex] = {...action.payload, quantity: state.cart[existItemIndex].quantity + 1}
20 | } else {
21 | state.cart.push(action.payload);
22 | }
23 | },
24 | prepare(item) {
25 | return { payload: { ...item, quantity: 1 } };
26 | },
27 | },
28 | updateItemQuantity: (state, action) => {
29 |
30 | const itemIndex = state.cart.findIndex((item) => item.productId === action.payload.productId)
31 | if (action.payload.quantity > 0) {
32 |
33 | if (itemIndex >= 0) {
34 | state.cart[itemIndex] = {...state.cart[itemIndex], quantity: action.payload.quantity}
35 | }
36 | }
37 |
38 | },
39 | removeItem: (state, action) => {
40 | state.cart = state.cart.filter((item) => item.productId !== action.payload);
41 | },
42 | emptyCart: (state) => {
43 | state.cart=[]
44 | }
45 | },
46 | });
47 |
48 | export const { addItem, removeItem , updateItemQuantity, emptyCart} = cartSlice.actions;
49 |
50 | export default cartSlice.reducer;
51 |
--------------------------------------------------------------------------------
/src/redux/productSelector.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from "@reduxjs/toolkit";
2 |
3 | export const selectProduct = createSelector(
4 | (state) => state.products.products,
5 | (_, productId) => productId,
6 | (products, productId) => products.find((product) => product.id === productId)
7 | );
8 |
--------------------------------------------------------------------------------
/src/redux/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { db } from "../firebase.utils";
3 |
4 | export const productsSlice = createSlice({
5 | name: "products",
6 | initialState: {
7 | products: null,
8 | departments:null
9 | },
10 | reducers: {
11 | setProducts: (state, action) => {
12 | state.products = action.payload;
13 | },
14 | setDepartment: (state, action) => {
15 | state.departments = action.payload
16 | }
17 | },
18 | });
19 |
20 | export const fetchProducts = () => {
21 | return (dispatch) => {
22 | const productsRef = db.collectionGroup("products");
23 | let products = [];
24 | productsRef
25 | // .limit(1)
26 | .get()
27 | .then((querySnapShot) => {
28 | querySnapShot.forEach((doc) => {
29 | products.push({ ...doc.data(), id: doc.id });
30 | });
31 | dispatch(setProducts(products));
32 | });
33 | };
34 | };
35 |
36 | export const fetchDepartment = () => {
37 | return (dispatch) => {
38 | const productsRef = db.collection("amazon");
39 | let departments = [];
40 | productsRef
41 | // .limit(1)
42 | .get()
43 | .then((querySnapShot) => {
44 | querySnapShot.forEach((doc) => {
45 | departments.push({ ...doc.data(), id: doc.id });
46 | });
47 | dispatch(setDepartment(departments));
48 | });
49 | };
50 | };
51 |
52 |
53 | export const { setProducts, setDepartment } = productsSlice.actions;
54 |
55 | export const selectProducts = (state) => state.products.products;
56 | export const selectDepartments = (state) => state.products.departments;
57 |
58 | export default productsSlice.reducer;
59 |
--------------------------------------------------------------------------------
/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit";
2 | import { persistReducer, persistStore } from "redux-persist";
3 | import { combineReducers } from "redux";
4 | import storage from "redux-persist/lib/storage";
5 | import userReducer from "./userSlice";
6 | import productsReducer from "./productsSlice";
7 | import cartReducer from "./cartSlice";
8 |
9 | const rootReducer = combineReducers({
10 | user: userReducer,
11 | products: productsReducer,
12 | cart: cartReducer,
13 | });
14 |
15 | const persistConfig = {
16 | key: "root",
17 | storage,
18 | whitelist: ["cart", "product"],
19 | };
20 |
21 |
22 | const persistedReducer = persistReducer(persistConfig, rootReducer)
23 |
24 | const store = configureStore({
25 | reducer: persistedReducer,
26 | middleware: getDefaultMiddleware({
27 | serializableCheck: {
28 | ignoredActions:["persist/PERSIST"]
29 | }
30 | })
31 | });
32 |
33 | const persistor = persistStore(store)
34 |
35 | export { store, persistor };
--------------------------------------------------------------------------------
/src/redux/userSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { db } from "../firebase.utils";
3 |
4 | // "ducks" pattern which suggests that you should put all your action creators and reducers in one file, do named exports of the action creators, and a default export of the reducer function.
5 |
6 | export const userSlice = createSlice({
7 | name: "user",
8 | initialState: {
9 | user: null,
10 | },
11 | reducers: {
12 | setUser: (state, action) => {
13 | state.user = action.payload;
14 | },
15 | },
16 | });
17 |
18 | export const fetchUser = (uid) => {
19 | return (dispatch) => {
20 | db.collection('amazonUsers').doc(uid)
21 | .get()
22 | .then((doc) => {
23 | if (doc.exists) {
24 | dispatch(setUser({name: doc.data().name, email: doc.data().email, uid: doc.id}));
25 | }
26 | })
27 | .catch((error) => console.log(error.message));
28 | };
29 | };
30 |
31 | export const { setUser } = userSlice.actions;
32 |
33 | // The function below is called a thunk and allows us to perform async logic. It
34 | // can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
35 | // will call the thunk with the `dispatch` function as the first argument. Async
36 | // code can then be executed and other actions can be dispatched
37 | // export const incrementAsync = amount => dispatch => {
38 | // setTimeout(() => {
39 | // dispatch(incrementByAmount(amount));
40 | // }, 1000);
41 | // };
42 |
43 | // The function below is called a selector and allows us to select a value from
44 | // the state. Selectors can also be defined inline where they're used instead of
45 | // in the slice file. For example: `useSelector((state) => state.user.value)`
46 | export const selectUser = (state) => state.user.user;
47 |
48 | export default userSlice.reducer;
49 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Navigate } from "react-router-dom";
3 | import Home from "./views/home/home";
4 | import Shop from "./views/shop/shop";
5 | import NotFoundView from "./views/errors/NotFoundView";
6 | import MainLayout from "./layouts/MainLayout";
7 | import Register from "./views/auth/Register";
8 | import Login from "./views/auth/Login";
9 | import AuthLayout from "./layouts/AuthLayout";
10 | import Checkout from "./views/checkout/Checkout";
11 | import Product from "./views/product/Product";
12 | import Payment from "./views/payment/Payment";
13 | import { loadStripe } from "@stripe/stripe-js";
14 | import { Elements } from "@stripe/react-stripe-js";
15 | import Orders from "./views/orders/Orders";
16 | import Mockup from "./mockup";
17 | import Success from "./views/payment/Success";
18 |
19 | const promise = loadStripe(
20 | "pk_test_51HeTtQImEgmfO9dx3rV5DTtE8sIiZOUG8KZbdXdfOEAzOG54ej7Xivl2kXmgMrHGV8dLRSlLioJ56v4KbB0bkMnP00UWd0uuAL"
21 | );
22 |
23 |
24 | const routes = [
25 | {
26 | path: "/",
27 | element: ,
28 | children: [
29 | {
30 | path: "shop",
31 | element: ,
32 | },
33 | {
34 | path:'checkout',
35 | element:
36 | },
37 | {
38 | path: 'payment',
39 | element:
40 | },
41 | {
42 | path: 'orders',
43 | element:
44 | },
45 | {
46 | path: 'success/:orderId',
47 | element:
48 | },
49 | {
50 | path: 'mockup',
51 | element:
52 | },
53 | {
54 | path: 'product/:productId',
55 | element:
56 | },
57 | {
58 | path: "404",
59 | element: ,
60 | },
61 | {
62 | path: "/",
63 | element: ,
64 | },
65 | {
66 | path: "*",
67 | element: ,
68 | },
69 | ],
70 | },
71 | {
72 | path: "auth",
73 | element: ,
74 | children: [
75 | {
76 | path: "register",
77 | element: ,
78 | },
79 | {
80 | path: "/",
81 | element: ,
82 | },
83 | ],
84 | },
85 | ];
86 |
87 | export default routes;
88 |
--------------------------------------------------------------------------------
/src/scss/_basic.scss:
--------------------------------------------------------------------------------
1 | @import "./variables";
2 |
3 | *,
4 | *::before,
5 | *::after {
6 | margin: 0;
7 | padding: 0;
8 | box-sizing: border-box;
9 | }
10 |
11 | body {
12 | min-height: 100%;
13 | font-family: -apple-system, BlinkMacSystemFont, "Amazon Ember", "Segoe UI",
14 | "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
15 | "Helvetica Neue", sans-serif;
16 | -webkit-font-smoothing: antialiased;
17 | -moz-osx-font-smoothing: grayscale;
18 | // background-color: $background-color !important;
19 | }
20 |
21 | code {
22 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
23 | monospace;
24 | }
25 |
26 | .ant-btn-primary {
27 | background-color: $button-color !important;
28 | border: 1px solid $button-border !important;
29 | color: $primary-color-light !important;
30 | }
31 |
32 | .ant-btn:hover {
33 | border: 1px solid $button-border !important;
34 | color: $primary-color-light !important;
35 | }
36 |
37 | .anticon-close {
38 | color: white !important;
39 | }
40 |
41 | .ant-drawer-title {
42 | color: white !important;
43 | font-weight: bold !important;
44 | font-size: 1.2rem !important;
45 | }
46 |
47 | .image-gallery-thumbnail-image {
48 | width: 60% !important;
49 | }
50 |
51 | .image-gallery-image {
52 | width: 80% !important;
53 | }
--------------------------------------------------------------------------------
/src/scss/_mixins.scss:
--------------------------------------------------------------------------------
1 | /* Breakpoints */
2 | @mixin breakpoint($point) {
3 | @if $point == xs {
4 | @media (min-width: 30rem) { @content; }
5 | }
6 | @else if $point == s {
7 | @media (min-width: 40rem) { @content; }
8 | }
9 | @else if $point == m {
10 | @media (min-width: 48rem) { @content; }
11 | }
12 | @else if $point == l {
13 | @media (min-width: 60rem) { @content; }
14 | }
15 | @else if $point == xl {
16 | @media (min-width: 64rem) { @content; }
17 | }
18 | @else if $point == xxl {
19 | @media (min-width: 80rem) { @content; }
20 | }
21 | }
22 |
23 | /** clear fix */
24 |
25 |
26 | @mixin clearfix() {
27 | &::after {
28 | display: block;
29 | clear: both;
30 | content: "";
31 | }
32 | }
--------------------------------------------------------------------------------
/src/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | $primary-color-main:#131A22;
2 | $primary-color-light:#232F3E;
3 |
4 | $secondary-color-main:#F1C55A;
5 | $secondary-color-light:#F0C459;
6 | $secondary-color-dark:#FF794A;
7 |
8 | $background-color:#EAEDED;
9 | $background-color-light:#fff;
10 |
11 | $text-color:#fff;
12 | $text-color-grey:#918382;
13 | $text-color-blue:#0167C0;
14 | $text-color-red:#B12705;
15 |
16 | $button-color:#F1C964;
17 | $button-border:#A88733;
18 |
19 | $star-color:#e67e22;
20 |
21 | $line-color:#dddddd;
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/views/auth/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useNavigate } from "react-router-dom";
3 | import { Form, Button, Spinner } from "react-bootstrap";
4 | import RouterLink from "../../components/link/RouterLink";
5 | import { Formik } from "formik";
6 | import * as Yup from "yup";
7 | import { auth } from "../../firebase.utils";
8 | import { message } from "antd";
9 | import { useDispatch } from "react-redux";
10 | import { fetchUser } from "../../redux/userSlice";
11 |
12 |
13 | const Login = () => {
14 | const navigate = useNavigate();
15 | const [loading, setLoading] = useState(false);
16 | const dispatch = useDispatch();
17 |
18 | return (
19 |
20 |
{
36 | setLoading(true);
37 | setSubmitting(true);
38 | auth
39 | .signInWithEmailAndPassword(values.email, values.password)
40 | .then((user) => {
41 | dispatch(fetchUser(user.user.uid))
42 | setLoading(false);
43 | navigate("/shop");
44 | })
45 | .catch((err) => {
46 | message.error(err.message);
47 | setLoading(false);
48 | });
49 |
50 | // resetForm();
51 | setSubmitting(false);
52 | }}
53 | >
54 | {({
55 | values,
56 | errors,
57 | touched,
58 | handleChange,
59 | handleBlur,
60 | handleSubmit,
61 | isSubmitting,
62 | }) => (
63 |
67 | Email
68 |
76 | {touched.email && errors.email && (
77 | {errors.email}
78 | )}
79 |
80 |
81 |
82 | Password
83 |
92 | {touched.password && errors.password && (
93 | {errors.password}
94 | )}
95 |
96 |
110 |
111 |
112 | By continuing, you agree to Amazon's Conditions of Use and
113 | Privacy Notice.
114 |
115 |
116 | New to Amazon ?
117 |
118 |
119 |
120 |
123 |
124 |
125 | )}
126 |
127 |
128 | );
129 | };
130 |
131 | export default Login;
132 |
--------------------------------------------------------------------------------
/src/views/auth/Login.scss:
--------------------------------------------------------------------------------
1 | .login {
2 | padding: 2rem;
3 | background-color: #fff;
4 |
5 |
6 | &__form {
7 | border: 1px solid #dddddd;
8 | border-radius: 0.2rem;
9 | width: 30%;
10 | min-width: 25rem;
11 | margin: 0 auto;
12 | padding: 1rem;
13 | background-color: #fff;
14 |
15 | &--title {
16 | font-size: 1.5rem;
17 | font-weight: 600;
18 | }
19 |
20 | &--label {
21 | font-weight: bold;
22 | font-size: medium;
23 | }
24 | &--button {
25 | background-color: $button-color !important;
26 | border: 1px solid $button-border !important;
27 | color: $primary-color-light !important;
28 | }
29 | &--link {
30 | background-color: $background-color !important;
31 | border: 1px solid $text-color-grey !important;
32 | color: $primary-color-light !important;
33 | }
34 | &--footer{
35 | font-size: smaller;
36 | margin: 1rem auto;
37 |
38 | & span {
39 | color: $text-color-grey;
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/views/auth/Register.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useNavigate } from "react-router-dom";
3 | import { Form, Button, Spinner, Alert } from "react-bootstrap";
4 | import { Formik } from "formik";
5 | import * as Yup from "yup";
6 | import RouterLink from "./../../components/link/RouterLink";
7 | import { auth, createUserProfileDocument } from "../../firebase.utils";
8 |
9 | const Register = () => {
10 | const [loading, setLoading] = useState(false);
11 | const [show, setShow] = useState(false);
12 | const [error, setError] = useState("");
13 | const navigate = useNavigate();
14 |
15 | return (
16 |
17 |
setShow(false)}
21 | dismissible
22 | >
23 | Oops! You got an error!
24 | {error}
25 |
26 |
{
55 | setLoading(true);
56 | setSubmitting(true);
57 |
58 | auth
59 | .createUserWithEmailAndPassword(values.email, values.password)
60 | .then((user) => {
61 | createUserProfileDocument(user, { name: values.name });
62 | })
63 | .then(() => {
64 | setLoading(false)
65 | navigate("/auth");
66 | })
67 | .catch((err) => {
68 | setError(err.message);
69 | setLoading(false);
70 | setShow(true);
71 | });
72 |
73 | resetForm();
74 | setSubmitting(false);
75 | }}
76 | >
77 | {({
78 | values,
79 | errors,
80 | touched,
81 | handleChange,
82 | handleBlur,
83 | handleSubmit,
84 | isSubmitting,
85 | }) => (
86 |
89 |
90 | Your name
91 |
92 |
100 | {touched.name && errors.name && (
101 | {errors.name}
102 | )}
103 |
104 |
105 | Email
106 |
114 | {touched.email && errors.email && (
115 | {errors.email}
116 | )}
117 |
118 |
119 |
120 |
121 | Password
122 |
123 |
133 | {touched.password && errors.password && (
134 | {errors.password}
135 | )}
136 |
137 |
138 |
139 | Re-enter password
140 |
141 |
151 | {touched.passwordConfirmation && errors.passwordConfirmation && (
152 |
153 | {errors.passwordConfirmation}
154 |
155 | )}
156 |
157 |
171 |
172 |
173 | By creating an account, you agree to Amazon's Conditions of Use
174 | and Privacy Notice.
175 |
176 |
177 |
178 | Already have an account ? Sign-In
179 |
180 |
181 |
182 |
183 | )}
184 |
185 |
186 | );
187 | };
188 |
189 | export default Register;
190 |
--------------------------------------------------------------------------------
/src/views/auth/Register.scss:
--------------------------------------------------------------------------------
1 | .register {
2 | padding: 2rem;
3 | background-color: #fff;
4 |
5 | &__form {
6 | border: 1px solid #dddddd;
7 | border-radius: 0.2rem;
8 | width: 30%;
9 | min-width: 25rem;
10 | margin: 0 auto;
11 | padding: 1rem;
12 | background-color: #fff;
13 |
14 | &--title {
15 | font-size: 1.5rem;
16 | font-weight: 600;
17 | }
18 |
19 | &--label {
20 | font-weight: bold;
21 | font-size: medium;
22 | }
23 | &--button {
24 | background-color: $button-color !important;
25 | border: 1px solid $button-border !important;
26 | color: $primary-color-light !important;
27 | }
28 | &--footer{
29 | font-size: smaller;
30 | margin: 1rem auto;
31 |
32 | & span {
33 | font-weight: bold;
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/views/checkout/Checkout.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CheckoutCard from "../../components/card/CheckoutCard";
3 | import { Button } from "react-bootstrap";
4 | import { selectCartItems } from "../../redux/cartSelector";
5 | import { useSelector } from "react-redux";
6 | import { selectCartItemsCount, selectSubtotal } from "../../redux/cartSelector";
7 | import RouterLink from "../../components/link/RouterLink";
8 | import {useNavigate} from 'react-router-dom';
9 |
10 | const Checkout = () => {
11 | const cartItems = useSelector((state) => selectCartItems(state));
12 | const cartCount = useSelector((state) => selectCartItemsCount(state));
13 | const subtotal = useSelector((state) => selectSubtotal(state));
14 |
15 | const navigate = useNavigate();
16 | const formatedSubtotal = subtotal.toLocaleString("en-US", {
17 | style: "currency",
18 | currency: "USD",
19 | });
20 |
21 | return (
22 |
23 |
24 |

29 |
30 | Shopping Cart
31 | Price
32 |
33 | {cartItems.length > 0 ? (
34 | cartItems.map(({ productId, ...props }) => (
35 |
39 | ))
40 | ) : (
41 |
42 |
Your Amazon Cart is empty.
43 |
44 | Check your Saved for later items below or{" "}
45 |
46 | continue shopping
47 |
48 | .
49 |
50 |
51 | )}
52 | {cartItems.length > 0 && (
53 |
54 | Subtotal({cartCount} items):
55 | {formatedSubtotal}
56 |
57 | )}
58 |
59 |
60 |
61 | Subtotal({cartCount} items):
62 | {formatedSubtotal}
63 |
64 |
65 |
68 |
69 |
70 |
71 | );
72 | };
73 |
74 | export default Checkout;
75 |
--------------------------------------------------------------------------------
/src/views/checkout/Checkout.scss:
--------------------------------------------------------------------------------
1 | .checkout {
2 | @include breakpoint(l){
3 | display: flex;
4 | }
5 | padding: 1rem;
6 |
7 | &__cart {
8 | flex-grow: 1;
9 | &__ad {
10 | width: 70vw;
11 | padding-top: 0.2rem;
12 | }
13 | &__title {
14 | display: flex;
15 | justify-content: space-between;
16 | margin-left: 0.5rem;
17 | margin-top: 1rem;
18 | border-bottom: 1px solid $line-color;
19 | & span:first-child {
20 | font-size: 1.6rem;
21 | margin-bottom: 0.5rem;
22 | }
23 | & span:last-child {
24 | align-self: flex-end;
25 | }
26 | }
27 | &__message {
28 | margin: 1rem;
29 | & > p {
30 | font-weight: 600;
31 | font-size: 1.8rem;
32 | }
33 | }
34 | &__subtotal {
35 | text-align: right;
36 | margin: 1rem auto;
37 | & span:first-child {
38 | font-weight: 600;
39 | }
40 | & span:last-child {
41 | font-weight: 800;
42 | }
43 | }
44 | &__card {
45 | content: "";
46 | height: 1px;
47 | background: $line-color;
48 | width: 100%;
49 | margin-left: 0.5rem;
50 | }
51 | }
52 | &__subtotal {
53 | border: 1px solid $line-color;
54 | border-radius: 0.2rem;
55 | background-color: $background-color;
56 | width: 18rem;
57 | min-width: 18rem;
58 | height: max-content;
59 | margin: 1rem;
60 | padding: 1rem;
61 |
62 | &__title {
63 | margin-bottom: 1rem;
64 | & span:first-child {
65 | margin-bottom: 1rem;
66 | font-weight: 600;
67 | }
68 | & span:last-child {
69 | margin-bottom: 1rem;
70 | font-weight: 800;
71 | }
72 | }
73 | &__button {
74 | background-color: $button-color !important;
75 | border: 1px solid $button-border !important;
76 | color: $primary-color-light !important;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/views/errors/NotFoundView.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Result } from 'antd';
3 |
4 | const NotFoundView = () => {
5 | return (
6 |
12 | )
13 | }
14 |
15 | export default NotFoundView
16 |
--------------------------------------------------------------------------------
/src/views/home/home.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import homeHeroShip from "../../assets/home-bg-ship.jpg";
3 | import homeHeroPrime from "../../assets/home-bg-prime.jpg";
4 | import homeHeroComputer from "../../assets/home-bg-computer.jpg";
5 | import homeHeroAlexa from "../../assets/home-bg-alexa.jpg";
6 | import homeHeroNZ from '../../assets/home-bg-nz.jpg'
7 | import { Carousel } from "react-bootstrap";
8 | import ShopBySection from "../../components/home/ShopBySection";
9 | import PopularSection from "../../components/home/PopularSection";
10 | import RecommendSection from "../../components/home/RecommendSection";
11 | import BestSellers from './../../components/home/BestSellers';
12 | import RelatedSection from "../../components/home/RelatedSection";
13 |
14 | const Home = () => {
15 | return (
16 |
17 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | );
75 | };
76 |
77 | export default Home;
78 |
--------------------------------------------------------------------------------
/src/views/home/home.scss:
--------------------------------------------------------------------------------
1 | .home {
2 | max-width: 1500px;
3 | margin: 0 auto;
4 | &__hero {
5 | width: 100%;
6 | mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
7 | margin: -1rem auto -3rem auto;
8 | @include breakpoint(m) {
9 | margin: -1rem auto -16rem auto;
10 | }
11 | }
12 |
13 | &__content {
14 | margin: 0 1rem;
15 | }
16 | }
17 |
18 |
19 | .carousel-control-next-icon, .carousel-control-prev-icon {
20 | position: absolute;
21 | top:7rem;
22 | width: 32px !important;
23 | height: 32px !important;
24 | }
25 |
--------------------------------------------------------------------------------
/src/views/orders/Orders.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { db } from "../../firebase.utils";
3 | import { selectUser } from "../../redux/userSlice";
4 | import { useSelector } from "react-redux";
5 | import Order from "./../../components/order/Order";
6 | import Spinner from "react-bootstrap/Spinner";
7 | import { useNavigate } from "react-router-dom";
8 | import { Result } from "antd";
9 |
10 | const Orders = () => {
11 | const [orders, setOrders] = useState([]);
12 | const user = useSelector(selectUser);
13 | const navigate = useNavigate();
14 |
15 | useEffect(() => {
16 | let mounted = true;
17 | if (user && mounted) {
18 | db.collection("amazonUsers")
19 | .doc(user?.uid)
20 | .collection("orders")
21 | .orderBy("created", "desc")
22 | .onSnapshot((snapshot) => {
23 | if (mounted) {
24 | setOrders(
25 | snapshot.docs.map((doc) => ({
26 | id: doc.id,
27 | data: doc.data(),
28 | }))
29 | );
30 | }
31 | });
32 | } else {
33 | setOrders([]);
34 | navigate("/auth");
35 | }
36 | return () => (mounted = false);
37 | }, [user, navigate]);
38 |
39 | return (
40 |
41 | {orders.length > 0 ? (
42 |
Your Orders
43 | ) : (
44 |
49 | )}
50 |
51 |
52 | {orders ? (
53 | orders.map((order) => )
54 | ) : (
55 |
60 | Loading...
61 |
62 | )}
63 |
64 |
65 | );
66 | };
67 |
68 | export default Orders;
69 |
--------------------------------------------------------------------------------
/src/views/orders/Orders.scss:
--------------------------------------------------------------------------------
1 | .orders{
2 | padding: 20px 80px;
3 |
4 | & > h1 {
5 | padding: 30px 0;
6 | }
7 | }
--------------------------------------------------------------------------------
/src/views/payment/Payment.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useNavigate } from "react-router-dom";
3 | import { useSelector, useDispatch } from "react-redux";
4 | import { selectUser } from "../../redux/userSlice";
5 | import { emptyCart } from "../../redux/cartSlice";
6 | import CheckoutCard from "../../components/card/CheckoutCard";
7 | import Spinner from "react-bootstrap/Spinner";
8 | import {
9 | selectCartItemsCount,
10 | selectSubtotal,
11 | selectCartItems,
12 | } from "../../redux/cartSelector";
13 | import { Link } from "react-router-dom";
14 | import { useStripe, useElements, CardElement } from "@stripe/react-stripe-js";
15 | import CurrencyFormat from "react-currency-format";
16 | import axios from "../../axios";
17 | import { db } from "../../firebase.utils";
18 |
19 | const Payment = () => {
20 | const navigate = useNavigate();
21 | const dispatch = useDispatch();
22 |
23 | const user = useSelector(selectUser);
24 | const cartItems = useSelector((state) => selectCartItems(state));
25 | const cartCount = useSelector((state) => selectCartItemsCount(state));
26 | const subtotal = useSelector((state) => selectSubtotal(state));
27 |
28 | const [error, setError] = useState(null);
29 | const [disabled, setDisabled] = useState(true);
30 | const [processing, setProcessing] = useState("");
31 | const [succeeded, setSucceeded] = useState(false);
32 | const [clientSecret, setClientSecret] = useState(true);
33 |
34 | const stripe = useStripe();
35 | const elements = useElements();
36 |
37 | useEffect(() => {
38 | let unsubscribe = false;
39 |
40 | const getClientSecret = async () => {
41 | try {
42 | const response = await axios({
43 | method: "post",
44 | // Stripe expects the total in a currencies subunits
45 | url: `/payments/create/${subtotal * 100}`,
46 | });
47 | if (!unsubscribe) setClientSecret(response.data.clientSecret);
48 | } catch (error) {
49 | console.log(error);
50 | }
51 | };
52 | getClientSecret();
53 | return () => {
54 | unsubscribe = true;
55 | };
56 | }, [subtotal]);
57 |
58 | const handleSubmit = async (event) => {
59 | event.preventDefault();
60 | setProcessing(true);
61 |
62 | await stripe
63 | .confirmCardPayment(clientSecret, {
64 | payment_method: {
65 | card: elements.getElement(CardElement),
66 | },
67 | })
68 | .then(({ paymentIntent }) => {
69 | // paymentIntent = payment confirmation
70 | db.collection("amazonUsers")
71 | .doc(user?.uid)
72 | .collection("orders")
73 | .doc(paymentIntent.id)
74 | .set({
75 | cartItems,
76 | amount: paymentIntent.amount,
77 | created: paymentIntent.created,
78 | });
79 | setSucceeded(true);
80 | setError(null);
81 | setProcessing(false);
82 | dispatch(emptyCart());
83 | navigate(`/success/${paymentIntent.id}`);
84 | });
85 | };
86 |
87 | const handleChange = (event) => {
88 | setDisabled(event.empty);
89 | setError(event.error ? event.error.message : "");
90 | };
91 |
92 | return (
93 |
94 |
95 |
96 |
97 | Checkout ({cartCount} items)
98 |
99 |
100 |
101 |
Delivery Address
102 |
103 |
104 |
{user?.name}
105 |
18 Great South Rd
106 |
Auckland , New Zealand
107 |
108 |
109 |
110 |
111 |
112 | Review items
113 | and delivery
114 |
115 |
116 |
117 | {cartItems.length > 0 &&
118 | cartItems.map(({ productId, ...props }) => (
119 |
123 | ))}
124 |
125 |
126 |
127 |
128 |
Payment Method
129 |
130 |
166 |
167 |
168 |
169 | );
170 | };
171 |
172 | export default Payment;
173 |
--------------------------------------------------------------------------------
/src/views/payment/Payment.scss:
--------------------------------------------------------------------------------
1 | .payment {
2 | background-color: white;
3 | &__container {
4 | & > h1 {
5 | text-align: center;
6 | padding: 10px;
7 | font-weight: 400;
8 | background-color: rgb(234,237,237);
9 | border-bottom: 1px solid lightgray;
10 |
11 | & a{
12 | text-decoration: none;
13 | }
14 | }
15 | }
16 |
17 | &__section {
18 | display: flex;
19 | padding: 20px;
20 | margin: 0 20px;
21 | border-bottom: 1px solid lightgray;
22 | }
23 |
24 | &__title {
25 | flex: 0.2;
26 | }
27 |
28 | &__address, &__items,&__details {
29 | flex: 0.8;
30 | }
31 |
32 | &__address p {
33 | font-size: 1.2rem;
34 | }
35 |
36 | &__details > form {
37 | max-width: 400px;
38 | }
39 |
40 | &__details > h3 {
41 | padding-bottom: 20px;
42 | }
43 | &__details > form > div > button{
44 | background: #f0c14b;
45 | border-radius: 2px;
46 | width: 100%;
47 | height: 30px;
48 | border: 1px solid;
49 | font-weight: bold;
50 | margin-top: 10px;
51 | border-color: #a88724 #9c7e31 #846a29;
52 | color: #111;
53 | }
54 |
55 | &__priceContainer {
56 | margin-top: 2rem;
57 |
58 | & > p {
59 | color: lightgray;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/views/payment/Success.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Result, Button } from "antd";
3 | import { useParams, useNavigate } from 'react-router-dom'
4 |
5 |
6 | const Success = () => {
7 | const { orderId } = useParams()
8 | const navigate = useNavigate()
9 |
10 | const handleOrders = () => {
11 | navigate('/orders')
12 | }
13 | const handleShopping = () => {
14 | navigate('/shop')
15 | }
16 | return (
17 |
23 | See all orders
24 | ,
25 | ,
26 | ]}
27 | />
28 | );
29 | };
30 |
31 | export default Success;
32 |
--------------------------------------------------------------------------------
/src/views/product/Product.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDispatch } from "react-redux";
3 | import { Button } from "react-bootstrap";
4 | import { useParams, useNavigate } from "react-router-dom";
5 | import { useSelector } from "react-redux";
6 | import { selectProduct } from "../../redux/productSelector";
7 | import { ReactComponent as AddToCart } from "../../assets/add_shopping_cart.svg";
8 | import { addItem } from "../../redux/cartSlice";
9 | import ImageGallery from "react-image-gallery";
10 | import "react-image-gallery/styles/css/image-gallery.css";
11 | import { Modal } from "antd";
12 | import { ExclamationCircleOutlined } from "@ant-design/icons";
13 | import { selectProducts } from "../../redux/productsSlice";
14 | import ProductCard from "../../components/card/ProductCard";
15 |
16 | import Slider from "react-slick";
17 | import "slick-carousel/slick/slick.css";
18 | import "slick-carousel/slick/slick-theme.css";
19 |
20 |
21 |
22 | const Product = () => {
23 | //related slider
24 | let slidesToShow;
25 | const width =
26 | window.innerWidth ||
27 | document.documentElement.clientWidth ||
28 | document.body.clientWidth;
29 | if (width > 1024) {
30 | slidesToShow = 7;
31 | } else if (width > 768) {
32 | slidesToShow = 5;
33 | } else if(width === 768) {
34 | slidesToShow = 4;
35 | } else {
36 | slidesToShow = 1;
37 | }
38 |
39 | const settings = {
40 | infinite: true,
41 | speed: 500,
42 | slidesToShow,
43 | slidesToScroll: 1,
44 | };
45 |
46 | const dispatch = useDispatch();
47 | const navigate = useNavigate();
48 | const { productId } = useParams();
49 | const {
50 | imgUrl,
51 | brand,
52 | title,
53 | price,
54 | count,
55 | rate,
56 | active,
57 | description,
58 | gallery,
59 | } = useSelector((state) => selectProduct(state, productId));
60 |
61 | const products = useSelector(selectProducts);
62 |
63 | const formatedPrice = price.toLocaleString("en-US", {
64 | style: "currency",
65 | currency: "USD",
66 | });
67 | const descriptions = description.split(";");
68 | const handleShopCart = () => {
69 | dispatch(
70 | addItem({
71 | title,
72 | price,
73 | count,
74 | imgUrl,
75 | rate,
76 | productId,
77 | active,
78 | })
79 | );
80 | Modal.confirm({
81 | title: "Product added to your cart",
82 | icon: ,
83 | content: `You've added the ${title} to your cart`,
84 | okText: "Continue Shopping",
85 | cancelText: "Proceed to Checkout",
86 | onOk: () => navigate("/shop"),
87 | onCancel: () => navigate("/checkout"),
88 | });
89 | };
90 |
91 | return (
92 |
93 |
94 |
95 |
101 |
102 |
103 |
{title}
104 |
105 | Brand:
106 | {brand}
107 |
108 |
109 | Price: {formatedPrice}
110 |
111 |
112 |
113 |
114 | {descriptions.map((el, index) => (
115 | - {el}
116 | ))}
117 |
118 |
119 |
120 |
121 |
{formatedPrice}
122 |
123 |
130 |
131 |
132 |
133 |
134 |
Inspired by your recent shopping trends
135 |
136 | {products.map(({ id, ...props }) => (
137 |
138 | ))}
139 |
140 |
141 |
142 | );
143 | };
144 |
145 | export default Product;
146 |
--------------------------------------------------------------------------------
/src/views/product/Product.scss:
--------------------------------------------------------------------------------
1 | .product {
2 | @include breakpoint(l) {
3 | display: flex;
4 | }
5 | justify-content: space-between;
6 | margin: 2rem 1rem;
7 | &__gallery {
8 | flex-basis: 50%;
9 | margin-right: 3rem;
10 | width: fit-content;
11 | overflow: hidden;
12 | }
13 | &__description {
14 | display: flex;
15 | flex-direction: column;
16 | flex-basis: 40%;
17 | &__title {
18 | font-weight: 600;
19 | font-size: 1.1rem;
20 | line-height: 1.5;
21 | }
22 | &__brand {
23 | font-weight: 600;
24 | color: $text-color-blue;
25 | font-size: 0.8rem;
26 | margin: 0.8rem 0;
27 | }
28 | &__price span:first-child {
29 | color: $text-color-grey;
30 | font-weight: 600;
31 | }
32 | &__price span:last-child {
33 | color: $text-color-red;
34 | font-weight: 600;
35 | }
36 |
37 | &__line {
38 | content: "";
39 | height: 1px;
40 | background: $line-color;
41 | width: 100%;
42 | margin-left: 0.5rem;
43 | margin: 1.5rem 0;
44 | }
45 |
46 | &__content li {
47 | padding-bottom: 1rem;
48 | line-height: 1.5;
49 | }
50 |
51 | &__content {
52 | padding-left: 1rem;
53 | }
54 | }
55 | &__cart {
56 | flex-basis: 10%;
57 | border: 1px solid $line-color;
58 | border-radius: 0.2rem;
59 | background-color: $background-color;
60 | width: 18rem;
61 | min-width: 18rem;
62 | height: max-content;
63 | margin: 1rem;
64 | padding: 1rem;
65 | &__price {
66 | margin-bottom: 1rem;
67 | color: $text-color-red;
68 | font-weight: 600;
69 | }
70 | }
71 |
72 | &__related {
73 | margin: 2rem;
74 | @include breakpoint(l) {
75 | margin: 4rem;
76 | }
77 |
78 | & > h3 {
79 | color: $secondary-color-dark;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/views/shop/shop.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ShopPage from "../../components/shop/ShopPage";
3 | import { useSelector } from "react-redux";
4 | import {
5 | selectProducts,
6 | selectDepartments,
7 | } from "../../redux/productsSlice.js";
8 |
9 | const Shop = () => {
10 | const products = useSelector(selectProducts);
11 | const departments = useSelector(selectDepartments);
12 | return (
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default Shop;
20 |
--------------------------------------------------------------------------------
/src/views/shop/shop.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaroncodehub/amazon-clone-react-redux/3e5c8952334f10cab16e724a6a10932976d12d61/src/views/shop/shop.scss
--------------------------------------------------------------------------------