diff --git a/README.md b/README.md index c9c919208..5b8abaade 100644 --- a/README.md +++ b/README.md @@ -1,28 +1 @@ -Assignment #2 & #3 - -Assignment: Create my Shoppingify app. Use any front-end libraries of your choice. Link it to your API that was done on assignment #1. Fulfill user stories below: - - User story 1: When I select the items tab, I can see a list of items under different categories. - User story 2: I can add a new item with name, category, note, and image. - User story 3: When I add a new item, I can select one from the existing categories or add a new one if the category does not exist - User story 4: When I select an item, I can see its details and I can choose to add the current list or delete the item. - User story 5: I can add items to the current list - User story 6 (optional): I can increase the number of item in the list - User story 7: I can remove the item from the list - User story 8: I can save/update the list with a name (user can have only one active list at a time) - User story 9: I can toggle between editing state and completing state - User story 10: When I am at completing state, I can save my progress by selecting the item - User story 11: I can cancel the active list - User story 12: When I try to cancel a list, I can see a confirmation notification - User story 13: I can see my shopping history and I can see the details of it - User story 14 (optional): I can see some statistics: top items, top categories, and monthly comparison. (Tips: use libraries like recharts for the graph) - User story (optional): I can search for items - -Here is the link to the Figma Design that you need to implement. Also here is the list of steps that you need to follow to achieve this assignment: - - Step 1: Fork and Clone the repository on Github - Step 2: Copy resources, README.md to your repository - Step 3: Login to Figma to checkout font, color, spacing,.. - Step 4: Complete all required user stories - Step 5: Update README.md - Step 6: Deploy your app and submit your solution +Assignment-2 diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 000000000..12b6f6272 --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/source.svg b/assets/source.svg new file mode 100644 index 000000000..cd71187f2 --- /dev/null +++ b/assets/source.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/undraw_shopping_app_flsj 1.svg b/assets/undraw_shopping_app_flsj 1.svg new file mode 100644 index 000000000..939a31080 --- /dev/null +++ b/assets/undraw_shopping_app_flsj 1.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 000000000..a32652e5b --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "Assignment-2", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.13.0", + "@testing-library/react": "^11.2.7", + "@testing-library/user-event": "^12.8.3", + "axios": "^0.21.1", + "dotenv": "^10.0.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-dotenv": "^0.1.3", + "react-scripts": "4.0.3", + "react-select": "^4.3.1", + "react-spinners": "^0.11.0", + "web-vitals": "^1.1.2" + }, + "scripts": { + "start": "react-dotenv && react-scripts start", + "build": "react-dotenv &&react-scripts build", + "serve": "react-dotenv && serve build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "react-dotenv": { + "whitelist": [ + "BASE_URL" + ] + } +} diff --git a/src/App.css b/src/App.css new file mode 100644 index 000000000..20c9eea18 --- /dev/null +++ b/src/App.css @@ -0,0 +1,7 @@ + grid-column-gap: 10px; +} +.main{ + background-color: aqua; + background-color:#fafafa; + grid-area: "main"; +} diff --git a/src/App.js b/src/App.js new file mode 100644 index 000000000..9cef750e1 --- /dev/null +++ b/src/App.js @@ -0,0 +1,60 @@ +import './App.css'; +import ItemsList from './components/ItemsList/ItemsList' +import SideNav from './components/SideNav/SideNav' +import RightPanel from './components/RightPanel/RightPanel' +import { useState, useEffect } from 'react' +import axios from 'axios'; + +function App() { + let [emptyBasket, setEmptyBasket] = useState(true); + let [basketList, setBasketList] = useState([]) + let [mainItems, setMainItems] = useState([ + { category: "No Elements", elements: [{ id: 1, label: "" }, { id: 2, label: "" }, { id: 3, label: "" }] }, + ]) + let [mainPanel, setMainPanel] = useState('ITEMS_LIST'); + useEffect(async () => { + const result = await axios.get( + 'http://localhost:1234/Items' + , { + headers: { + 'Authorization': "bearerToken eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaWF0IjoxNjE5ODg1MjU2fQ.LxUB938hxe-m5IXZsTsjiUAa9g1oiLJ6mfryPkRbid8" + } + }).then(res => { + setMainItems(res.data); + }); + + }, []) + + function addToBucket(newItem) { + setEmptyBasket(false); + let data = [...basketList]; + let categoryIndex = data.findIndex((element) => element.Category === newItem.Category); + if (categoryIndex != -1) { + let itemIndex = data[categoryIndex].Items.findIndex((item) => Item.label === newItem.label); + if (itemIndex != -1) { + data[categoryIndex].Items[itemIndex].q++; + } else { + data[categoryIndex].Items.push({ ...newItem, q: 1 }); + } + } else { + data.push({ Category: newItem.Category, Items: [{ ...newItem, q: 1 }] }); + } + console.log(data); + setBasketList(data); + } + let categories = mainItems.map(element => element.Category) + function toggleMainPage(panel) { + setMainPannel(panel); + } + return ( +
+ + { + mainPanel == 'ITEMS_LIST' ? :

+ } + +
+ ); +} + +export default App; diff --git a/src/components/Basket/Basket.css b/src/components/Basket/Basket.css new file mode 100644 index 000000000..8b5fb674a --- /dev/null +++ b/src/components/Basket/Basket.css @@ -0,0 +1,120 @@ +.basketWrapper { + height: fit-content; + width : 350px ; + background: #FFF0DE; +} + +.addItemWrapper{ + width: 290.76px; + height: 125px; + background: #80485B; + border-radius: 24px; + margin : 43px ; + display : flex; + flex-direction: row; + justify-content: start; + align-items:center; +} +.addItemWrapper h2 { + text-align: left; + margin-left:30px; + font-weight: bold; + font-size: 16px; + line-height: 20px; + color: #FFFFFF; + width:170px; + margin-bottom: 13px; +} +.addItemWrapper button { + margin-left:30px; + background: #FFFFFF; + box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.04); + border-radius: 12px; + border:none; + margin : 0; + padding : 11px 29px; + cursor: pointer; +} +.labelWrapper { + display: inline-flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; +} +.labelWrapper h2 { + margin-left:48px; + font-style: normal; + font-weight: bold; + font-size: 24px; + line-height: 30px; + color: #34333A; + padding : 0px ; + + +} +.labelWrapper span { + font-size : 20px; + margin-right: 31px; +} +.itemWrapper { + margin : 0 45px; + text-align: left; +} +.itemWrapper h1 { + color :#828282; + font-weight: 500; + font-size: 14px; + line-height: 17px; +} +.infoWrapper { + display: flex; + flex-direction: row; + justify-content: space-between; + +} +.infoWrapper h2 { + font-weight: 500; + font-size: 18px; + line-height: 22px; + color: #000000; +} +.submitBtn { + width: 100% ; + background: #FFFFFF; + padding: 34px 39px; +} +.inputWrapper{ + width:309px; + height: 61px; + border: 2px solid #F9A109; + box-sizing: border-box; + border-radius: 12px; + display: flex; + flex-direction: row; +} +.inputWrapper input { + border : none; + border-radius: 12px; + color : #BDBDBD; + padding:5px 25px; +} +.inputWrapper button { + width:100px; + border: 0; + background: #F9A109; + border-radius: 12px; + color : #FFFFFF; + font-weight: bold; + font-size: 16px; + line-height: 20px; + cursor: pointer; +} +.emptyLabel { + font-family: Quicksand; + font-weight: bold; + font-size: 20px; + line-height: 25px; + color: #34333A; + margin : 100px 0 ; +} diff --git a/src/components/Basket/Basket.js b/src/components/Basket/Basket.js new file mode 100644 index 000000000..197f35851 --- /dev/null +++ b/src/components/Basket/Basket.js @@ -0,0 +1,95 @@ +import ClipLoader from "react-spinners/ClipLoader"; + +import './Basket.css' +import { ReactComponent as BottleLogo } from '../../assets/source.svg' +import { ReactComponent as NoItemLogo } from '../../assets/no_item.svg' +import { useState } from 'react' +import axios from 'axios' +function Basket(props) { + + let list = props.basketList + console.log(list); + let [inputLabel, setInputLabel] = useState("Please Enter A Name") + let [empty, setEmpty] = useState(props.empty); + + function addNewItem() { + props.addNewItem(); + } + + if (empty !== props.empty) { + setEmpty(props.empty); + } + + function saveBasket() { + setEmpty(true); + + axios.post('http://localhost:8000/lists/lists', { + name: inputLabel, + items: list.map(element => element.items) + }, { + headers: { + 'Authorization': "bearerToken eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaWF0IjoxNjE5ODg1MjU2fQ.LxUB938hxe-m5IXZsTsjiUAa9g1oiLJ6mfryPkRbid8", + 'Content-Type': 'application/json' + }, + }).then(res => { + console.log(res) + }) + + } + return ( +
+
+ +
+

Did not Find what you need ?

+ +
+
+ +
+ {empty ? +
+ + +
+ : +
+
+

Shopping List

+ edit +
+ { + list.map((element) => { + return ( +
+

{element.category}

+ { + element.items.map((item) => { + return ( +
+

{item.label}

+

{item.q}

+
+ ) + }) + } +
+ ) + }) + } +
+ } + +
+
+ setInputLabel('')} onChange={(e) => { setInputLabel(e.target.value) }} > + +
+
+
+
+ + ) +} + +export default Basket diff --git a/src/components/ItemList/ItemList.css b/src/components/ItemList/ItemList.css new file mode 100644 index 000000000..46cc98afe --- /dev/null +++ b/src/components/ItemList/ItemList.css @@ -0,0 +1,103 @@ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(https://example.com/MaterialIcons-Regular.eot); /* For IE6-8 */ + src: local('Material Icons'), + local('MaterialIcons-Regular'), + url(https://example.com/MaterialIcons-Regular.woff2) format('woff2'), + url(https://example.com/MaterialIcons-Regular.woff) format('woff'), + url(https://example.com/MaterialIcons-Regular.ttf) format('truetype'); + } +.wrapper { + width : 900px; + margin : 30px 80px; + margin-right : 10px; +} +.categoryWrapper { + margin : 20px 0 ; +} +.categoryWrapper h1 { + text-align: left; + margin :10px 10px; + font-size: 22px; +} + +.itemsWrapper { + width: 100%; + display: inline-flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: start; +} +.itemBtn { + background: #FFFFFF; + width: 182px; + height: fit-content; + margin: 10px 10px; + + box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.05); + border-radius:12px ; + border : none; + align-items: center; + display: inline-flex; + justify-content: space-around; + cursor : pointer +} +.itemsWrapper :hover { + box-shadow: 0px 15px 25px rgba(0, 0, 0, 0.05); + +} +.itemBtn h2 { + font-size: 18px; +} +.itemBtn span { + margin-left: 10px; + margin-right: 15; + color : #C1C1C4; + width:14px; +} + +/* HEADER */ +.header { + display : flex; + flex-direction: row; + height : fit-content; + align-items: center; + margin-bottom:57px; +} +.header h2 { + width :450px; + text-align: start; + font-family: Quicksand; + font-style: normal; + font-weight: bold; + font-size: 24px; + line-height: 32px; +} +.header h2 span { + color : #F9A109; +} + +.header div { + display : inline-flex; + align-items: center; + flex-direction: row; +} +.search{ + background: #FFFFFF; + box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.04); + border-radius: 12px; + padding : 16px; + display:inline-flex; +} +.search input { + border : none; + box-decoration-break: none; + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 17px; + color: #BDBDBD; + margin-left:23px; +} diff --git a/src/components/ItemList/ItemList.js b/src/components/ItemList/ItemList.js new file mode 100644 index 000000000..a20613851 --- /dev/null +++ b/src/components/ItemList/ItemList.js @@ -0,0 +1,51 @@ +import { useState } from 'react'; +import './ItemsList.css' +function ItemsList(props) { + let items = props.items + let [searchValue, setSearchValue] = useState('Search'); + + function addToBucket(item) { + props.addToBucket(item); + } + return ( +
+ +
+

Shoppingify allows you take your shopping list whenever you go

+
+ search + { setSearchValue(' ') }} onChange={(e) => { setSearchValue(e.target.value) }} > +
+
+ { + items.map((element) => { + return
+ +

{element.category}

+ +
+ { + element.elements.map((item) => { + return ( + + ) + + }) + } +
+ + +
+ }) + + } +
+ ) + +} + + +export default ItemsList; diff --git a/src/components/RightPanel/RightPanel.js b/src/components/RightPanel/RightPanel.js new file mode 100644 index 000000000..087934e14 --- /dev/null +++ b/src/components/RightPanel/RightPanel.js @@ -0,0 +1,27 @@ +import Basket from "../basket/Basket"; +import AddItem from "../addItem/addItem"; +import { useState } from "react"; +import './panel.css' +import itemsList from "../ItemsList/ItemsList"; + +function RightPannel(props) { + let basketList = props.basketList + let categories = props.categories + let empty = props.empty + let [addNew, setAddNew] = useState(false); + //a// + + function addNewItem() { + setAddNew(true); + } + + return ( +
+ {addNew ? setAddNew(false)} /> : } +
+ ) +} + +export default RightPannel; + + diff --git a/src/components/SideNav/SideNav.css b/src/components/SideNav/SideNav.css new file mode 100644 index 000000000..363c1454c --- /dev/null +++ b/src/components/SideNav/SideNav.css @@ -0,0 +1,19 @@ +.SideNav { + background: #FFFFFF; + height:100vh; + padding:5px; + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; +} +.iconsWrapper { + display : flex; + flex-direction : column; +} +.iconsWrapper span{ + margin : 25px 0 ; +} +span { + cursor: pointer; +} diff --git a/src/components/SideNav/SideNav.js b/src/components/SideNav/SideNav.js new file mode 100644 index 000000000..fc97c19c3 --- /dev/null +++ b/src/components/SideNav/SideNav.js @@ -0,0 +1,23 @@ +import './SideNav.css' +import { ReactComponent as ReactLogo } from '../../assets/logo.svg'; +import { ReactComponent as Logo } from '../../assets/source.svg' +function SideNav(props) { + function toggleMainPanel(panel) { + props.toggle(panel) + } + return ( +
+ +
+ + toggleMainPanel("ITEMS_LIST")}>Category + toggleMainPanel("HISTORY")}>refresh + toggleMainPanel("STATS")}>query_stats +
+ +
+ ) +} + + +export default SideNav; diff --git a/src/index.css b/src/index.css new file mode 100644 index 000000000..14e3bd40b --- /dev/null +++ b/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 000000000..36713d9ab --- /dev/null +++ b/src/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './app'; +import reportWebVitals from './reportWebVitals'; + + +ReactDOM.render( + + + , + document.getElementById('root') +); +reportWebVitals(console.log); diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js new file mode 100644 index 000000000..8ba775e17 --- /dev/null +++ b/src/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals;