diff --git a/README.md b/README.md
index a193f51..9c70004 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,9 @@
## Links and Resources
-[Code sand box][]
-![Lab 36 UML](../assets/Lab36.png)
+[Code sand box](https://codesandbox.io/p/github/ShadowDraco/storefront/main?workspaceId=a3383399-bf2b-4cb1-9efa-c698b2303ac8)
+![Lab 36 UML](./assets/Lab36.png)
+![Lab 37 UML](./assets/Lab37.png)
### What is this
@@ -26,6 +27,15 @@
- Identify that category as selected (change of class/display).
- Show a list of products associated with the category.
+### Phase 2 Requirements
+
+Add the "Add to Cart" feature to our application, which will allow our users to not only browse items in the store, but also select them and have them persist in their "shopping cart" for later purchase.
+
+- As a user, I want to choose from products in the list and add them to my shopping cart.
+- As a user, I want to see the products that I've added to my shopping cart in a growing list on the side of the page".
+- As a user, I want to change the quantity of items I intend to purchase in the header. i.e. CART (1)
+- As a user, I want to be able to remove an item from my shopping cart.
+
### Application Architecture
Virtual Store application using Vite, Material UI, and React-redux:
@@ -37,8 +47,10 @@ Virtual Store application using Vite, Material UI, and React-redux:
- Dispatches an action when one is clicked to "activate" it.
- A `` component:
- Displays a list of products associated with the selected category.
+- A `` component:
+ - Displays a short list (title only) of products in the cart.
+ This should be present at all times.
### Testing
-- Testing for the core behaviors (user stories) of the application is required.
-- Consider leveraging a chatbot as needed.
+- Tests are accomplished by using Vitest and react testing library.
diff --git a/assets/Lab36.png b/assets/Lab36.png
new file mode 100644
index 0000000..d7f8e3d
Binary files /dev/null and b/assets/Lab36.png differ
diff --git a/assets/Lab37.png b/assets/Lab37.png
new file mode 100644
index 0000000..585126f
Binary files /dev/null and b/assets/Lab37.png differ
diff --git a/package-lock.json b/package-lock.json
index 535a59a..b60e2dc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
+ "@mui/icons-material": "^5.13.7",
"@mui/material": "^5.13.7",
"@redux-devtools/extension": "^3.2.5",
"@testing-library/jest-dom": "^5.16.5",
@@ -18,6 +19,7 @@
"react-dom": "^18.2.0",
"react-redux": "^8.1.1",
"redux": "^4.2.1",
+ "sass": "^1.63.6",
"vitest": "^0.33.0"
},
"devDependencies": {
@@ -1172,6 +1174,31 @@
"url": "https://opencollective.com/mui"
}
},
+ "node_modules/@mui/icons-material": {
+ "version": "5.13.7",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.13.7.tgz",
+ "integrity": "sha512-zoVtkb9jYVUGfI7CobOdDBEAlpg3XESiO6cWqSDGwEma69+CBDIAwGpnO5truvQDJHBGSAfzFj3nObwxjkyA7Q==",
+ "dependencies": {
+ "@babel/runtime": "^7.22.5"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui"
+ },
+ "peerDependencies": {
+ "@mui/material": "^5.0.0",
+ "@types/react": "^17.0.0 || ^18.0.0",
+ "react": "^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@mui/material": {
"version": "5.13.7",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.7.tgz",
@@ -2041,6 +2068,18 @@
"node": ">=4"
}
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2196,6 +2235,14 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2335,6 +2382,43 @@
"node": "*"
}
},
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/ci-info": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
@@ -3646,6 +3730,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@@ -3701,7 +3796,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3710,7 +3804,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -4632,6 +4725,14 @@
"integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==",
"dev": true
},
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/nwsapi": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
@@ -5141,6 +5242,17 @@
"react-dom": ">=16.6.0"
}
},
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/redent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
@@ -5302,6 +5414,22 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"devOptional": true
},
+ "node_modules/sass": {
+ "version": "1.63.6",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz",
+ "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==",
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
@@ -6873,6 +7001,14 @@
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.7.tgz",
"integrity": "sha512-/suIo4WoeL/OyO3KUsFVpdOmKiSAr6NpWXmQ4WLSxwKrTiha1FJxM6vwAki5W/5kR9WnVLw5E8JC4oHHsutT8w=="
},
+ "@mui/icons-material": {
+ "version": "5.13.7",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.13.7.tgz",
+ "integrity": "sha512-zoVtkb9jYVUGfI7CobOdDBEAlpg3XESiO6cWqSDGwEma69+CBDIAwGpnO5truvQDJHBGSAfzFj3nObwxjkyA7Q==",
+ "requires": {
+ "@babel/runtime": "^7.22.5"
+ }
+ },
"@mui/material": {
"version": "5.13.7",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.7.tgz",
@@ -7496,6 +7632,15 @@
"color-convert": "^1.9.0"
}
},
+ "anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -7613,6 +7758,11 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
+ },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -7697,6 +7847,31 @@
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
"integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA=="
},
+ "chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "requires": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ }
+ }
+ },
"ci-info": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
@@ -8680,6 +8855,14 @@
"has-bigints": "^1.0.1"
}
},
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
"is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@@ -8713,14 +8896,12 @@
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
@@ -9387,6 +9568,11 @@
"integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==",
"dev": true
},
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
+ },
"nwsapi": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
@@ -9729,6 +9915,14 @@
"prop-types": "^15.6.2"
}
},
+ "readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
"redent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
@@ -9838,6 +10032,16 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"devOptional": true
},
+ "sass": {
+ "version": "1.63.6",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz",
+ "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==",
+ "requires": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ }
+ },
"saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
diff --git a/package.json b/package.json
index f064afb..4540bf9 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
+ "@mui/icons-material": "^5.13.7",
"@mui/material": "^5.13.7",
"@redux-devtools/extension": "^3.2.5",
"@testing-library/jest-dom": "^5.16.5",
@@ -21,6 +22,7 @@
"react-dom": "^18.2.0",
"react-redux": "^8.1.1",
"redux": "^4.2.1",
+ "sass": "^1.63.6",
"vitest": "^0.33.0"
},
"devDependencies": {
diff --git a/src/App.jsx b/src/App.jsx
index a9e3efc..b35f500 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,25 +1,35 @@
import { useState } from 'react';
-import './App.css';
+import './App.scss';
import Products from './components/Products';
import Categories from './components/Categories';
import Header from './components/Header';
import Footer from './components/Footer';
+import ActiveProduct from './components/ActiveProduct';
import { Box, Container } from '@mui/material';
-import AddButtons from './components/AddButtons';
+
+import { useSelector } from 'react-redux';
+import Cart from './components/Cart';
function App() {
- const [chosenCategory, setChosenCategory] = useState(0);
+ const currentProduct = useSelector(state => state.currentProduct);
return (
-
-
-
+
+ {!currentProduct.name ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
+
);
diff --git a/src/App.scss b/src/App.scss
new file mode 100644
index 0000000..6c58207
--- /dev/null
+++ b/src/App.scss
@@ -0,0 +1,37 @@
+$backgroundColor: rgb(255, 255, 255);
+$buttonBlue: rgb(72, 72, 233);
+
+body {
+ background-color: $backgroundColor;
+}
+
+#OURSTORE {
+ background-color: $backgroundColor;
+}
+
+#OURSTORE:hover {
+ cursor: pointer;
+ background-color: darken($backgroundColor, 5);
+}
+
+#active {
+ background-color: darken($backgroundColor, 5);
+}
+
+button.blue {
+ background-color: $buttonBlue;
+ color: white;
+}
+
+svg.trashButton {
+ background-color: $backgroundColor;
+}
+
+svg.trashButton:hover {
+ background-color: darken($backgroundColor, 5);
+}
+
+ul {
+ background-color: darken($backgroundColor, 2);
+ border-bottom: 5px gray dashed;
+}
diff --git a/src/components/ActiveProduct/ActiveProduct.test.jsx b/src/components/ActiveProduct/ActiveProduct.test.jsx
new file mode 100644
index 0000000..7eed34b
--- /dev/null
+++ b/src/components/ActiveProduct/ActiveProduct.test.jsx
@@ -0,0 +1,24 @@
+import { render, screen, fireEvent } from '@testing-library/react';
+import ActiveProduct from '.';
+import store from '../../redux/store';
+import { Provider } from 'react-redux';
+
+test('renders selected product card', () => {
+ const product = {
+ name: 'TV',
+ description: 'See all the things',
+ url: `${import.meta.env.VITE_UNSPLASH_URL}tv`,
+ price: 600,
+ inventory: Math.random() * 20 + 10,
+ };
+ render(
+
+
+
+ );
+
+ const cardElement = screen.getByText(/TV/i);
+ const relatedElement = screen.getByText(/Related Items/i);
+ expect(cardElement).toBeInTheDocument();
+ expect(relatedElement).toBeInTheDocument();
+});
diff --git a/src/components/ActiveProduct/index.jsx b/src/components/ActiveProduct/index.jsx
new file mode 100644
index 0000000..dfc2d65
--- /dev/null
+++ b/src/components/ActiveProduct/index.jsx
@@ -0,0 +1,92 @@
+import { useSelector, useDispatch } from 'react-redux';
+import PropTypes from 'prop-types';
+
+import {
+ Container,
+ Card,
+ CardActions,
+ CardMedia,
+ CardContent,
+ Button,
+ Typography,
+ Box,
+} from '@mui/material';
+
+export default function ActiveProduct({ product }) {
+ const currentProduct = product
+ ? product
+ : useSelector(state => state.currentProduct);
+ const dispatch = useDispatch();
+
+ return (
+
+
+ {currentProduct.name}
+
+
+
+
+
+
+ In Stock: {currentProduct.inventory}
+
+ ${currentProduct.price}
+
+
+
+
+
+
+ Related Items
+
+
+ Product Details
+
+
+
+
+ );
+}
+
+ActiveProduct.propTypes = {
+ product: PropTypes.object,
+};
diff --git a/src/components/AddButtons.jsx b/src/components/AddButtons.jsx
deleted file mode 100644
index 90f69b2..0000000
--- a/src/components/AddButtons.jsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { useDispatch } from 'react-redux';
-import { updateCategories } from '../redux/reducers/categories';
-import { updateProducts } from '../redux/reducers/products';
-
-import { Box, Button } from '@mui/material';
-
-import PropTypes from 'prop-types';
-
-export default function AddButtons({ chosenCategory }) {
- const testCategory = {
- CONSTRUCTION: 'How did we get all of this',
- };
-
- const testElectronic = {
- Phone: {
- productType: 0,
- description: 'call all the people',
- url: `${import.meta.env.VITE_UNSPLASH_URL}phone`,
- },
- };
- const testFood = {
- Strawberry: {
- productType: 1,
- description: 'sweet or sour?',
- url: `${import.meta.env.VITE_UNSPLASH_URL}strawberry`,
- },
- };
-
- const dispatch = useDispatch();
- return (
-
-
-
-
- );
-}
-
-AddButtons.propTypes = {
- chosenCategory: PropTypes.number,
-};
diff --git a/src/components/Cart/cart.test.jsx b/src/components/Cart/cart.test.jsx
new file mode 100644
index 0000000..005e891
--- /dev/null
+++ b/src/components/Cart/cart.test.jsx
@@ -0,0 +1,15 @@
+import { render, screen, fireEvent } from '@testing-library/react';
+import Cart from '.';
+
+import { Provider } from 'react-redux';
+import store from '../../redux/store';
+test('renders cart', () => {
+ render(
+
+
+
+ );
+
+ const cartElement = screen.getByTestId('CART');
+ expect(cartElement).toBeInTheDocument();
+});
diff --git a/src/components/Cart/index.jsx b/src/components/Cart/index.jsx
new file mode 100644
index 0000000..5cd4b59
--- /dev/null
+++ b/src/components/Cart/index.jsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import { setCurrentProduct } from '../../redux/reducers/currentProduct';
+import { Box, List, ListItem } from '@mui/material';
+import { removeFromCart } from '../../redux/reducers/cart';
+import Delete from '@mui/icons-material/Delete';
+
+export default function Cart() {
+ const dispatch = useDispatch();
+ const cart = useSelector(state => state.cart);
+
+ return (
+
+
+ {cart.map((item, i) => {
+ return (
+
+ {
+ dispatch(setCurrentProduct(item));
+ }}
+ >
+ {item.name}
+
+ dispatch(removeFromCart(item))}
+ />
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/Categories/index.jsx b/src/components/Categories/index.jsx
index b7592f6..f9ea692 100644
--- a/src/components/Categories/index.jsx
+++ b/src/components/Categories/index.jsx
@@ -1,16 +1,14 @@
-import { useSelector } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
+import { chooseCategory } from '../../redux/reducers/categories';
import { ButtonGroup, Button, Box, Typography, Container } from '@mui/material';
import PropTypes from 'prop-types';
-import { useState } from 'react';
-export default function Categories({ setChosenCategory }) {
+export default function Categories() {
+ const chosenCategory = useSelector(state => state.categories.chosenCategory);
const categories = useSelector(state => state.categories);
- const [categoryString, setCategoryString] = useState('ELECTRONICS');
- const [categoryDescription, setCategoryDescription] = useState(
- 'Tvs, laptops and more!'
- );
+ const dispatch = useDispatch();
return (
<>
@@ -20,18 +18,24 @@ export default function Categories({ setChosenCategory }) {
variant='text'
aria-label='text button group'
>
- {categories.map((product, i) => {
- let entries = Object.entries(product);
- return (
+ {Object.entries(categories).map((category, i) => {
+ return category[0] === 'chosenCategory' ? (
+ ''
+ ) : (
);
})}
@@ -43,20 +47,16 @@ export default function Categories({ setChosenCategory }) {
mt={8}
data-testid='CATEGORY'
>
- {categoryString}
+ {chosenCategory.string}
- {categoryDescription}
+ {chosenCategory.description}
>
);
}
-
-Categories.propTypes = {
- setChosenCategory: PropTypes.func,
-};
diff --git a/src/components/Header/Header.test.jsx b/src/components/Header/Header.test.jsx
index 5e1c6d4..5d49b32 100644
--- a/src/components/Header/Header.test.jsx
+++ b/src/components/Header/Header.test.jsx
@@ -1,7 +1,14 @@
import { render, screen, fireEvent } from '@testing-library/react';
import Header from '../Header';
+
+import { Provider } from 'react-redux';
+import store from '../../redux/store';
test('renders Header links', () => {
- render();
+ render(
+
+
+
+ );
const headerElement = screen.getByText(/OUR STORE/i);
expect(headerElement).toBeInTheDocument();
diff --git a/src/components/Header/index.jsx b/src/components/Header/index.jsx
index 89bbebf..6b0ae9a 100644
--- a/src/components/Header/index.jsx
+++ b/src/components/Header/index.jsx
@@ -1,6 +1,10 @@
import { Typography, AppBar, Toolbar, Button } from '@mui/material';
+import { useSelector, useDispatch } from 'react-redux';
+import { setCurrentProduct } from '../../redux/reducers/currentProduct';
export default function Header() {
+ const dispatch = useDispatch();
+ const cart = useSelector(state => state.cart);
return (
<>
- OUR STORE
-
+ {
+ dispatch(setCurrentProduct({}));
+ }}
+ >
+ OUR STORE
+
+
>
diff --git a/src/components/Products/Products.test.jsx b/src/components/Products/Products.test.jsx
index 577d5d0..06a76d9 100644
--- a/src/components/Products/Products.test.jsx
+++ b/src/components/Products/Products.test.jsx
@@ -6,7 +6,7 @@ import { Provider } from 'react-redux';
test('renders product card', () => {
render(
-
+
);
diff --git a/src/components/Products/index.jsx b/src/components/Products/index.jsx
index 48304db..a20cfde 100644
--- a/src/components/Products/index.jsx
+++ b/src/components/Products/index.jsx
@@ -1,4 +1,4 @@
-import { useSelector } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import {
@@ -11,22 +11,28 @@ import {
Typography,
Box,
} from '@mui/material';
+import { setCurrentProduct } from '../../redux/reducers/currentProduct';
+import { addToCart } from '../../redux/reducers/cart';
-export default function Products({ chosenCategory }) {
- const products = useSelector(state => state.products[chosenCategory]);
-
- const mappableProducts = Object.entries(Object.entries(products)[0][1]);
+export default function Products() {
+ const chosenCategory = useSelector(state => state.categories.chosenCategory);
+ const products = useSelector(state => state.products);
+ const dispatch = useDispatch();
return (
-
+
- {mappableProducts.map((product, i) => {
+ {Object.entries(products)[chosenCategory.index][1].map((product, i) => {
return (
- {product[0]}
+ {product.name}
- {product[1].description}
+ {product.description}
-
-
+
+
);
@@ -54,7 +74,3 @@ export default function Products({ chosenCategory }) {
);
}
-
-Products.propTypes = {
- chosenCategory: PropTypes.number,
-};
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index e69de29..0000000
diff --git a/src/App.css b/src/index.scss
similarity index 100%
rename from src/App.css
rename to src/index.scss
diff --git a/src/main.jsx b/src/main.jsx
index 5804ad6..dd19046 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
-import './index.css';
+import './index.scss';
import store from './redux/store.js';
import { Provider } from 'react-redux';
diff --git a/src/redux/reducers/cart.js b/src/redux/reducers/cart.js
new file mode 100644
index 0000000..f9775bc
--- /dev/null
+++ b/src/redux/reducers/cart.js
@@ -0,0 +1,32 @@
+export const initialCart = [];
+
+const cart = (state = initialCart, action) => {
+ switch (action.type) {
+ case 'add':
+ return [...state, action.payload];
+ case 'remove':
+ const stateCopy = [...state];
+ const newState = stateCopy.filter(
+ item => item.name !== action.payload.name
+ );
+ return newState;
+ default:
+ return state;
+ }
+};
+
+export const addToCart = item => {
+ return {
+ type: 'add',
+ payload: item,
+ };
+};
+
+export const removeFromCart = item => {
+ return {
+ type: 'remove',
+ payload: item,
+ };
+};
+
+export default cart;
diff --git a/src/redux/reducers/cart.test.js b/src/redux/reducers/cart.test.js
new file mode 100644
index 0000000..6981b28
--- /dev/null
+++ b/src/redux/reducers/cart.test.js
@@ -0,0 +1,10 @@
+import cart, { addToCart, removeFromCart } from './cart';
+
+describe('cart reducer', () => {
+ it('handles initial state', () => {
+ expect(addToCart({})).toEqual({
+ payload: {},
+ type: 'add',
+ });
+ });
+});
diff --git a/src/redux/reducers/categories.js b/src/redux/reducers/categories.js
index ad83512..f121a14 100644
--- a/src/redux/reducers/categories.js
+++ b/src/redux/reducers/categories.js
@@ -1,17 +1,35 @@
-export const initialCategories = [
- { ELECTRONICS: 'Tvs, laptops and more!' },
- { FOOD: 'Everything tasty and savory' },
-];
+export const initialCategories = {
+ ELECTRONICS: 'Tvs, laptops and more!',
+ FOOD: 'Everything tasty and savory',
+ chosenCategory: {
+ index: 0,
+ string: 'ELECTRONICS',
+ description: 'Tvs, laptops and more!',
+ },
+};
const categories = (state = initialCategories, action) => {
switch (action.type) {
case 'updateCategories':
- return [...state, action.payload];
+ return categoryUpdate(state, action);
+ case 'chooseCategory':
+ return { ...state, chosenCategory: action.payload };
default:
return state;
}
};
+//? unused because not required
+const categoryUpdate = (state, action) => {
+ const stateCopy = { ...state };
+ stateCopy[action.payload.productType] = [
+ state[action.payload.productType],
+ action.payload,
+ ];
+
+ return stateCopy;
+};
+
export const updateCategories = newCategory => {
return {
type: 'updateCategories',
@@ -19,4 +37,11 @@ export const updateCategories = newCategory => {
};
};
+export const chooseCategory = category => {
+ return {
+ type: 'chooseCategory',
+ payload: category,
+ };
+};
+
export default categories;
diff --git a/src/redux/reducers/currentProduct.js b/src/redux/reducers/currentProduct.js
new file mode 100644
index 0000000..61e661e
--- /dev/null
+++ b/src/redux/reducers/currentProduct.js
@@ -0,0 +1,19 @@
+export const initialProduct = {};
+
+const currentProduct = (state = initialProduct, action) => {
+ switch (action.type) {
+ case 'set':
+ return action.payload;
+ default:
+ return state;
+ }
+};
+
+export const setCurrentProduct = product => {
+ return {
+ type: 'set',
+ payload: product,
+ };
+};
+
+export default currentProduct;
diff --git a/src/redux/reducers/currentProduct.test.js b/src/redux/reducers/currentProduct.test.js
new file mode 100644
index 0000000..8dd1ee6
--- /dev/null
+++ b/src/redux/reducers/currentProduct.test.js
@@ -0,0 +1,10 @@
+import currentProduct, { setCurrentProduct } from './currentProduct';
+
+describe('current product reducer', () => {
+ it('handles initial state', () => {
+ expect(setCurrentProduct({})).toEqual({
+ payload: {},
+ type: 'set',
+ });
+ });
+});
diff --git a/src/redux/reducers/products.js b/src/redux/reducers/products.js
index eecd3d4..8c30c56 100644
--- a/src/redux/reducers/products.js
+++ b/src/redux/reducers/products.js
@@ -1,36 +1,47 @@
-export const initialProducts = [
- {
- ELECTRONICS: {
- TV: {
- description: 'See all the things',
- url: 'https://source.unsplash.com/random?tv',
- },
+export const initialProducts = {
+ ELECTRONICS: [
+ {
+ name: 'TV',
+ description: 'See all the things',
+ url: `${import.meta.env.VITE_UNSPLASH_URL}tv`,
+ price: 600,
+ inventory: Math.floor(Math.random() * 20) + 10,
+ },
- Macbook: {
- description: 'Do all the things',
- url: 'https://source.unsplash.com/random?macbook',
- },
+ {
+ name: 'Macbook',
+ description: 'Do all the things',
+ url: `${import.meta.env.VITE_UNSPLASH_URL}macbook`,
+ price: 1400,
+ inventory: Math.floor(Math.random() * 20) + 5,
+ },
+ ],
+ FOOD: [
+ {
+ name: 'Potato',
+ description: 'versatile!',
+ url: `${import.meta.env.VITE_UNSPLASH_URL}potato`,
+ price: 6,
+ inventory: Math.floor(Math.random() * 100) + 10,
},
- },
- {
- FOOD: {
- Potato: {
- description: 'versatile!',
- url: 'https://source.unsplash.com/random?potato',
- },
- Carrot: {
- description: 'want good eyesight?',
- url: 'https://source.unsplash.com/random?carrot',
- },
+ {
+ name: 'Carrot',
+ description: 'want good eyesight?',
+ url: `${import.meta.env.VITE_UNSPLASH_URL}carrot`,
+ price: 4,
+ inventory: Math.floor(Math.random() * 100) + 20,
},
- },
-];
+ ],
+};
export const products = (state = initialProducts, action) => {
switch (action.type) {
case 'updateProducts':
return [...state, action.payload];
+ case 'selectProduct':
+ console.log('Selecting product', action);
+ return state;
default:
return state;
}
diff --git a/src/redux/store.js b/src/redux/store.js
index 8963cdc..35fc34f 100644
--- a/src/redux/store.js
+++ b/src/redux/store.js
@@ -1,11 +1,20 @@
-import { createStore, combineReducers } from 'redux';
+import { legacy_createStore as createStore, combineReducers } from 'redux';
import products from './reducers/products';
import categories from './reducers/categories';
+import currentProduct from './reducers/currentProduct';
+import cart from './reducers/cart';
+import { composeWithDevTools } from '@redux-devtools/extension';
-const rootReducer = combineReducers({
+const reducers = combineReducers({
products,
categories,
+ currentProduct,
+ cart,
});
-export default createStore(rootReducer);
+const store = () => {
+ return createStore(reducers, composeWithDevTools());
+};
+
+export default store();