From 214a99cb1c0d52772aac358c23d36a87ede9372c Mon Sep 17 00:00:00 2001 From: Mihir Date: Sat, 23 Sep 2023 15:17:39 -0700 Subject: [PATCH 1/7] login page done --- package-lock.json | 39 +++++++++++++++ package.json | 1 + src/App.js | 34 +++++++------ src/pages/login.jsx | 113 ++++++++++++++++++++++++++++++++++++++++++++ src/utils/utils.js | 4 ++ 5 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 src/pages/login.jsx create mode 100644 src/utils/utils.js diff --git a/package-lock.json b/package-lock.json index 89f4e0cd..6f963d34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@testing-library/user-event": "13.5.0", "react": "18.2.0", "react-dom": "18.2.0", + "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", "web-vitals": "2.1.4" } @@ -4530,6 +4531,14 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@remix-run/router": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz", + "integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -17275,6 +17284,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz", + "integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==", + "dependencies": { + "@remix-run/router": "1.9.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz", + "integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==", + "dependencies": { + "@remix-run/router": "1.9.0", + "react-router": "6.16.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/package.json b/package.json index ca2a9136..90494dce 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@testing-library/user-event": "13.5.0", "react": "18.2.0", "react-dom": "18.2.0", + "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", "web-vitals": "2.1.4" }, diff --git a/src/App.js b/src/App.js index 96d24665..a41587a0 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,10 @@ import React, { useEffect, useState } from 'react' +import { + createBrowserRouter, + RouterProvider, +} from "react-router-dom"; + +import Login from './pages/login'; import CssBaseline from '@mui/material/CssBaseline' @@ -12,6 +18,7 @@ import Stack from '@mui/material/Stack' import Content from './components/Content' import Loading from './components/Loading' +import ShoppingList from './pages/shoppingList'; let theme = createTheme({ @@ -33,7 +40,8 @@ let theme = createTheme({ background: '#fefefe', overflow: 'hidden', padding: '5px', - width: '450px', + width: '100vw', + height: '100vh', color: '#333', }, }, @@ -52,6 +60,16 @@ let theme = createTheme({ }) theme = responsiveFontSizes(theme) +const router = createBrowserRouter([ + { + path: "/", + element: , + }, + { + path: "/login", + element: , + }, +]); const App = () => { @@ -74,23 +92,11 @@ const App = () => { } }, []) - const renderLoading = () => { - if ( !loading ) { return } - return - } - - const renderContent = () => { - if ( loading ) { return } - return - } return ( - - {renderLoading()} - {renderContent()} - + ) } diff --git a/src/pages/login.jsx b/src/pages/login.jsx new file mode 100644 index 00000000..526d0bcd --- /dev/null +++ b/src/pages/login.jsx @@ -0,0 +1,113 @@ +import * as React from 'react'; +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import CssBaseline from '@mui/material/CssBaseline'; +import TextField from '@mui/material/TextField'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import Link from '@mui/material/Link'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; + +function Copyright(props) { + return ( + + {'Copyright © '} + + Your Website + {' '} + {new Date().getFullYear()} + {'.'} + + ); +} + +// TODO remove, this demo shouldn't need to reset the theme. + +const defaultTheme = createTheme(); + +export default function SignIn() { + const handleSubmit = (event) => { + event.preventDefault(); + const data = new FormData(event.currentTarget); + const user = { + email: data.get('email'), + password: data.get('password'), + }; + + }; + + return ( + + + + + + + + + Sign in + + + + + } + label="Remember me" + /> + + + + + Forgot password? + + + + + {"Don't have an account? Sign Up"} + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/utils/utils.js b/src/utils/utils.js new file mode 100644 index 00000000..79ce59a6 --- /dev/null +++ b/src/utils/utils.js @@ -0,0 +1,4 @@ +// save data to local storage +export const saveToLocalStorage = (key, value) => { + localStorage.setItem(key, JSON.stringify(value)); +}; \ No newline at end of file From 9aed4ea885ca12aea9583e029155e8565d55b595 Mon Sep 17 00:00:00 2001 From: Mihir Date: Sat, 23 Sep 2023 15:35:48 -0700 Subject: [PATCH 2/7] list page basic ui --- src/App.js | 15 ++++ src/pages/shoppingList.jsx | 174 +++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 src/pages/shoppingList.jsx diff --git a/src/App.js b/src/App.js index a41587a0..a1d8532e 100644 --- a/src/App.js +++ b/src/App.js @@ -56,6 +56,21 @@ let theme = createTheme({ }, }, }, + MuiTableHead: { + styleOverrides: { + root: { + backgroundColor: '#f5f5f5', + fontWeight: '600', + }, + }, + }, + MuiTableCell: { + styleOverrides: { + root: { + fontSize: '1.2rem', + } + } + }, }, }) theme = responsiveFontSizes(theme) diff --git a/src/pages/shoppingList.jsx b/src/pages/shoppingList.jsx new file mode 100644 index 00000000..5a963399 --- /dev/null +++ b/src/pages/shoppingList.jsx @@ -0,0 +1,174 @@ +import React, { useState } from 'react'; +import { Button, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, TextField, Typography, Popover, Box } from '@mui/material'; + +function ShoppingList() { + const initialItems = [ + { + id: 1, + title: 'Apples', + price: 2.99, + image: 'apple.jpg', // Replace with actual image URL + quantity: 3, + }, + { + id: 2, + title: 'Bananas', + price: 1.99, + image: 'banana.jpg', // Replace with actual image URL + quantity: 2, + }, + // Add more items here + ]; + + const [anchorEl, setAnchorEl] = useState(null); + const [items, setItems] = useState(initialItems); + const [newItem, setNewItem] = useState({ + title: '', + price: '', + image: '', + quantity: '', + }); + + const addItem = () => { + if ( + newItem.title.trim() === '' || + newItem.price.trim() === '' || + newItem.quantity.trim() === '' + ) { + return; + } + + const newItemWithId = { + ...newItem, + id: Date.now(), + price: parseFloat(newItem.price), + quantity: parseInt(newItem.quantity), + }; + + setItems([...items, newItemWithId]); + setNewItem({ + title: '', + price: '', + image: '', + quantity: '', + }); + }; + + const removeItem = (id) => { + const updatedItems = items.filter((item) => item.id !== id); + setItems(updatedItems); + }; + + + const openPopover = (event) => { + setAnchorEl(event.currentTarget); + }; + + const closePopover = () => { + setAnchorEl(null); + }; + + const isPopoverOpen = Boolean(anchorEl); + + return ( +
+ Shopping List + + + + setNewItem({ ...newItem, title: e.target.value })} + /> + setNewItem({ ...newItem, price: e.target.value })} + /> + setNewItem({ ...newItem, image: e.target.value })} + /> + setNewItem({ ...newItem, quantity: e.target.value })} + /> + + + + + + + + Item + Price + Image + Quantity + Total Price + Actions + + + + {items.map((item) => ( + + {item.title} + ${item.price.toFixed(2)} + + {item.title} + + {item.quantity} + ${(item.price * item.quantity).toFixed(2)} + + + + + ))} + +
+
+
+ ); +} + +export default ShoppingList; From 5d4b0f0164c28fa256b790ba774dbfadbc4c6c62 Mon Sep 17 00:00:00 2001 From: Mihir Date: Sat, 23 Sep 2023 16:35:44 -0700 Subject: [PATCH 3/7] refactor code --- src/App.js | 90 +++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/App.js b/src/App.js index a1d8532e..c93cb0ab 100644 --- a/src/App.js +++ b/src/App.js @@ -1,79 +1,83 @@ -import React, { useEffect, useState } from 'react' -import { - createBrowserRouter, - RouterProvider, -} from "react-router-dom"; +import React, { useEffect, useState } from "react"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; -import Login from './pages/login'; +import Login from "./pages/login"; -import CssBaseline from '@mui/material/CssBaseline' +import CssBaseline from "@mui/material/CssBaseline"; import { createTheme, ThemeProvider, responsiveFontSizes, -} from '@mui/material/styles' - -import Stack from '@mui/material/Stack' +} from "@mui/material/styles"; -import Content from './components/Content' -import Loading from './components/Loading' -import ShoppingList from './pages/shoppingList'; +import Stack from "@mui/material/Stack"; +import Content from "./components/Content"; +import Loading from "./components/Loading"; +import ShoppingList from "./pages/shoppingList"; let theme = createTheme({ palette: { primary: { - main: '#de6720', + main: "#de6720", }, secondary: { - main: '#0077ea', + main: "#0077ea", }, }, components: { MuiCssBaseline: { styleOverrides: { html: { - WebkitFontSmoothing: 'auto', + WebkitFontSmoothing: "auto", }, body: { - background: '#fefefe', - overflow: 'hidden', - padding: '5px', - width: '100vw', - height: '100vh', - color: '#333', + background: "#fefefe", + overflow: "hidden", + padding: "5px", + width: "100vw", + height: "100vh", + color: "#333", }, }, }, MuiLinearProgress: { styleOverrides: { root: { - backgroundColor: 'rgba(222, 103, 32, 0.25)', + backgroundColor: "rgba(222, 103, 32, 0.25)", }, bar: { - backgroundColor: 'rgba(222, 103, 32, 1)', + backgroundColor: "rgba(222, 103, 32, 1)", }, }, }, MuiTableHead: { styleOverrides: { root: { - backgroundColor: '#f5f5f5', - fontWeight: '600', + backgroundColor: "#f5f5f5", + fontWeight: "600", }, }, }, MuiTableCell: { styleOverrides: { root: { - fontSize: '1.2rem', - } - } + fontSize: "1.2rem", + }, + }, + }, + MuiPopover: { + styleOverrides: { + paper: { + minWidth: "80vw", + minHeight: "50vh", + }, + }, }, }, -}) -theme = responsiveFontSizes(theme) +}); +theme = responsiveFontSizes(theme); const router = createBrowserRouter([ { @@ -87,33 +91,29 @@ const router = createBrowserRouter([ ]); const App = () => { - - const [loading, setLoading] = useState(false) + const [loading, setLoading] = useState(false); useEffect(() => { - // show a loading indicator - setLoading(true) + setLoading(true); setTimeout(() => { - // hide loading indicator - setLoading(false) - }, 1000) // 1 second + setLoading(false); + }, 1000); // 1 second // hide loading indicator return () => { - setLoading(false) - } - }, []) - + setLoading(false); + }; + }, []); return ( - ) -} + ); +}; -export default App +export default App; From 902e15ce5aeca95912e03b3ce14756b35da7005b Mon Sep 17 00:00:00 2001 From: Mihir Date: Sat, 23 Sep 2023 16:36:33 -0700 Subject: [PATCH 4/7] - add item search api - add item search and add functionality --- src/api/products.js | 14 ++ src/pages/shoppingList.jsx | 319 ++++++++++++++++++++++--------------- 2 files changed, 204 insertions(+), 129 deletions(-) create mode 100644 src/api/products.js diff --git a/src/api/products.js b/src/api/products.js new file mode 100644 index 00000000..aebdce9c --- /dev/null +++ b/src/api/products.js @@ -0,0 +1,14 @@ +const API_URL = "https://dummyjson.com/products"; + +export const ProductsAPI = { + getAllProducts: async () => { + const response = await fetch(API_URL); + const data = await response.json(); + return data; + }, + searchProducts: async (query) => { + const response = await fetch(`${API_URL}/search?q=${query}`); + const data = await response.json(); + return data; + } +}; diff --git a/src/pages/shoppingList.jsx b/src/pages/shoppingList.jsx index 5a963399..f1a528ae 100644 --- a/src/pages/shoppingList.jsx +++ b/src/pages/shoppingList.jsx @@ -1,57 +1,51 @@ -import React, { useState } from 'react'; -import { Button, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, TextField, Typography, Popover, Box } from '@mui/material'; +import React, { useState } from "react"; +import { + Button, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + TextField, + Typography, + Popover, + Box, +} from "@mui/material"; +import { ProductsAPI } from "../api/products"; function ShoppingList() { const initialItems = [ { id: 1, - title: 'Apples', + title: "Apples", price: 2.99, - image: 'apple.jpg', // Replace with actual image URL + image: "apple.jpg", // Replace with actual image URL quantity: 3, }, { id: 2, - title: 'Bananas', + title: "Bananas", price: 1.99, - image: 'banana.jpg', // Replace with actual image URL + image: "banana.jpg", // Replace with actual image URL quantity: 2, }, // Add more items here ]; + const [searchTerm, setSearchTerm] = useState(""); + const [results, setResults] = useState([]); const [anchorEl, setAnchorEl] = useState(null); const [items, setItems] = useState(initialItems); - const [newItem, setNewItem] = useState({ - title: '', - price: '', - image: '', - quantity: '', - }); - const addItem = () => { - if ( - newItem.title.trim() === '' || - newItem.price.trim() === '' || - newItem.quantity.trim() === '' - ) { - return; - } - - const newItemWithId = { - ...newItem, - id: Date.now(), - price: parseFloat(newItem.price), - quantity: parseInt(newItem.quantity), - }; - - setItems([...items, newItemWithId]); - setNewItem({ - title: '', - price: '', - image: '', - quantity: '', - }); + const searchItems = async () => { + const res = await ProductsAPI.searchProducts(searchTerm); + const products = res.products.map((product) => ({ + ...product, + quantity: 1, + })); + setResults(products); }; const removeItem = (id) => { @@ -59,7 +53,6 @@ function ShoppingList() { setItems(updatedItems); }; - const openPopover = (event) => { setAnchorEl(event.currentTarget); }; @@ -70,103 +63,171 @@ function ShoppingList() { const isPopoverOpen = Boolean(anchorEl); + const ItemsPopover = ( + + + + + setSearchTerm(e.target.value)} + style={{ width: "50%" }} + /> + + + + + + + + Item + Price + Image + Quantity + Actions + + + + {results.map((item) => ( + + {item.title} + ${item.price.toFixed(2)} + + {item.title} + + + { + const updatedItem = { ...item, quantity: e.target.value }; + const updatedResults = results.map((result) => + result.id === item.id ? updatedItem : result + ); + setResults(updatedResults); + }} + /> + + + + + + ))} + +
+
+
+
+ ); + + const ShoppingTable = ( + + + + + Item + Price + Image + Quantity + Total Price + Actions + + + + {items.map((item) => ( + + {item.title} + ${item.price.toFixed(2)} + + {item.title} + + {item.quantity} + ${(item.price * item.quantity).toFixed(2)} + + + + + ))} + +
+
+ ); + return ( -
+
Shopping List - - - setNewItem({ ...newItem, title: e.target.value })} - /> - setNewItem({ ...newItem, price: e.target.value })} - /> - setNewItem({ ...newItem, image: e.target.value })} - /> - setNewItem({ ...newItem, quantity: e.target.value })} - /> - - - - - - - - Item - Price - Image - Quantity - Total Price - Actions - - - - {items.map((item) => ( - - {item.title} - ${item.price.toFixed(2)} - - {item.title} - - {item.quantity} - ${(item.price * item.quantity).toFixed(2)} - - - - - ))} - -
-
+ > + Add Item + + {ItemsPopover} + {ShoppingTable}
); } From e5e12fb7ee36348c4184704d93d1e419b2df0915 Mon Sep 17 00:00:00 2001 From: Mihir Date: Sat, 23 Sep 2023 17:05:48 -0700 Subject: [PATCH 5/7] add speech recognition --- src/pages/shoppingList.jsx | 53 +++++++++++++++++----- src/utils/speechRecognition.js | 81 ++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 src/utils/speechRecognition.js diff --git a/src/pages/shoppingList.jsx b/src/pages/shoppingList.jsx index f1a528ae..d4d77cfd 100644 --- a/src/pages/shoppingList.jsx +++ b/src/pages/shoppingList.jsx @@ -14,6 +14,7 @@ import { Box, } from "@mui/material"; import { ProductsAPI } from "../api/products"; +import { initializeSpeech, startRecognition } from "../utils/speechRecognition"; function ShoppingList() { const initialItems = [ @@ -38,6 +39,8 @@ function ShoppingList() { const [results, setResults] = useState([]); const [anchorEl, setAnchorEl] = useState(null); const [items, setItems] = useState(initialItems); + const [listening, setListening] = useState(false); + const [recognition, setRecognition] = useState(null); const searchItems = async () => { const res = await ProductsAPI.searchProducts(searchTerm); @@ -61,6 +64,27 @@ function ShoppingList() { setAnchorEl(null); }; + const startSpeechRecognition = () => { + const speechSearch = (term) => { + setSearchTerm(term); + searchItems(); + }; + + const toggleListening = () => { + setListening((listening) => !listening); + }; + + const recognition = initializeSpeech(); + setRecognition(recognition); + startRecognition(recognition, toggleListening, speechSearch, toggleListening); + }; + + const stopSpeechRecognition = () => { + recognition.stop(); + setRecognition(null); + setListening(false); + }; + const isPopoverOpen = Boolean(anchorEl); const ItemsPopover = ( @@ -79,8 +103,12 @@ function ShoppingList() { > - { - const updatedItem = { ...item, quantity: e.target.value }; - const updatedResults = results.map((result) => - result.id === item.id ? updatedItem : result - ); - setResults(updatedResults); + const updatedItem = { + ...item, + quantity: e.target.value, + }; + const updatedResults = results.map((result) => + result.id === item.id ? updatedItem : result + ); + setResults(updatedResults); }} /> @@ -147,10 +178,10 @@ function ShoppingList() { variant="contained" color="secondary" onClick={() => { - if(item.quantity < 1) return; + if (item.quantity < 1) return; setItems([...items, item]); - setResults([]) - setSearchTerm("") + setResults([]); + setSearchTerm(""); closePopover(); }} > diff --git a/src/utils/speechRecognition.js b/src/utils/speechRecognition.js new file mode 100644 index 00000000..abb7ac15 --- /dev/null +++ b/src/utils/speechRecognition.js @@ -0,0 +1,81 @@ +const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; +const SpeechGrammarList = window.SpeechGrammarList || window.webkitSpeechGrammarList; + +export const initializeSpeech = () => { + const recognition = new SpeechRecognition(); + const speechRecognitionList = new SpeechGrammarList(); + recognition.grammars = speechRecognitionList; + recognition.lang = 'en-US'; + recognition.interimResults = false; + recognition.maxAlternatives = 1; + return recognition; +} + +export const startRecognition = (recognition, onStart, onResult, onEnd) => { + + // To ensure case consistency while checking with the returned output text + recognition.start(); + onStart(); + + recognition.onresult = function(event) { + // The SpeechRecognitionEvent results property returns a SpeechRecognitionResultList object + // The SpeechRecognitionResultList object contains SpeechRecognitionResult objects. + // It has a getter so it can be accessed like an array + // The first [0] returns the SpeechRecognitionResult at position 0. + // Each SpeechRecognitionResult object contains SpeechRecognitionAlternative objects that contain individual results. + // These also have getters so they can be accessed like arrays. + // The second [0] returns the SpeechRecognitionAlternative at position 0. + // We then return the transcript property of the SpeechRecognitionAlternative object + const speechResult = event.results[0][0].transcript.toLowerCase(); + onResult(speechResult); + console.log('Confidence: ' + event.results[0][0].confidence); + } + + recognition.onspeechend = function() { + recognition.stop(); + onEnd(); + } + + recognition.onerror = function(event) { + console.log('Error occurred in recognition: ' + event.error); + } + + recognition.onaudiostart = function(event) { + //Fired when the user agent has started to capture audio. + console.log('Listening'); + } + + recognition.onaudioend = function(event) { + //Fired when the user agent has finished capturing audio. + console.log('Done listening'); + } + + recognition.onend = function(event) { + //Fired when the speech recognition service has disconnected. + console.log('SpeechRecognition.onend'); + } + + recognition.onnomatch = function(event) { + //Fired when the speech recognition service returns a final result with no significant recognition. This may involve some degree of recognition, which doesn't meet or exceed the confidence threshold. + console.log('Please try again.'); + } + + recognition.onsoundstart = function(event) { + //Fired when any sound — recognisable speech or not — has been detected. + console.log('SpeechRecognition.onsoundstart'); + } + + recognition.onsoundend = function(event) { + //Fired when any sound — recognisable speech or not — has stopped being detected. + console.log('SpeechRecognition.onsoundend'); + } + + recognition.onspeechstart = function (event) { + //Fired when sound that is recognised by the speech recognition service as speech has been detected. + console.log('SpeechRecognition.onspeechstart'); + } + recognition.onstart = function(event) { + //Fired when the speech recognition service has begun listening to incoming audio with intent to recognize grammars associated with the current SpeechRecognition. + console.log('SpeechRecognition.onstart'); + } +} \ No newline at end of file From 886fa8046fe0e070be90261d9dd81e333f71df53 Mon Sep 17 00:00:00 2001 From: Mihir Date: Sat, 23 Sep 2023 17:23:04 -0700 Subject: [PATCH 6/7] add download list --- src/pages/shoppingList.jsx | 61 +++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/src/pages/shoppingList.jsx b/src/pages/shoppingList.jsx index d4d77cfd..c2906300 100644 --- a/src/pages/shoppingList.jsx +++ b/src/pages/shoppingList.jsx @@ -19,20 +19,19 @@ import { initializeSpeech, startRecognition } from "../utils/speechRecognition"; function ShoppingList() { const initialItems = [ { - id: 1, + id: -1, title: "Apples", price: 2.99, - image: "apple.jpg", // Replace with actual image URL + thumbnail: "apple.jpg", quantity: 3, }, { id: 2, title: "Bananas", price: 1.99, - image: "banana.jpg", // Replace with actual image URL + thumbnail: "banana.jpg", quantity: 2, }, - // Add more items here ]; const [searchTerm, setSearchTerm] = useState(""); @@ -76,7 +75,12 @@ function ShoppingList() { const recognition = initializeSpeech(); setRecognition(recognition); - startRecognition(recognition, toggleListening, speechSearch, toggleListening); + startRecognition( + recognition, + toggleListening, + speechSearch, + toggleListening + ); }; const stopSpeechRecognition = () => { @@ -87,6 +91,34 @@ function ShoppingList() { const isPopoverOpen = Boolean(anchorEl); + const downloadList = () => { + const element = document.createElement("a"); + + const text = + "Shopping List: \n Item \t Price \t Quantity \t Total\n" + + items.map( + (item) => + `${item.title.padEnd(' ', 20)} \t $${item.price.toFixed(2)} \t ${ + item.quantity + } \t $${(item.price * item.quantity).toFixed(2)}` + ).join("\n"); + + console.log(text); + + element.setAttribute( + "href", + "data:text/plain;charset=utf-8," + encodeURIComponent(text) + ); + element.setAttribute("download", "ShoppingList.txt"); + + element.style.display = "none"; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + }; + const ItemsPopover = ( ${item.price.toFixed(2)} {item.title}{item.title} ${item.price.toFixed(2)} - {item.title} + {item.title} {item.quantity} ${(item.price * item.quantity).toFixed(2)} @@ -259,6 +296,16 @@ function ShoppingList() { {ItemsPopover} {ShoppingTable} +
+ {/* Download List Button */} + + {/* Share Via Email */} + +
); } From 5ef5727cc1c9078857beeeb487209c63cac9a2a1 Mon Sep 17 00:00:00 2001 From: Mihir Date: Sat, 23 Sep 2023 17:43:14 -0700 Subject: [PATCH 7/7] add send email --- src/pages/shoppingList.jsx | 42 ++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/pages/shoppingList.jsx b/src/pages/shoppingList.jsx index c2906300..fc43dedc 100644 --- a/src/pages/shoppingList.jsx +++ b/src/pages/shoppingList.jsx @@ -22,14 +22,14 @@ function ShoppingList() { id: -1, title: "Apples", price: 2.99, - thumbnail: "apple.jpg", + thumbnail: "apple.jpg", quantity: 3, }, { id: 2, title: "Bananas", price: 1.99, - thumbnail: "banana.jpg", + thumbnail: "banana.jpg", quantity: 2, }, ]; @@ -91,19 +91,24 @@ function ShoppingList() { const isPopoverOpen = Boolean(anchorEl); + const listToText = () => { + return ( + "Shopping List: \n Item \t Price \t Quantity \t Total\n" + + items + .map( + (item) => + `${item.title.padEnd(" ", 20)} \t $${item.price.toFixed(2)} \t ${ + item.quantity + } \t $${(item.price * item.quantity).toFixed(2)}` + ) + .join("\n") + ); + }; + const downloadList = () => { const element = document.createElement("a"); - const text = - "Shopping List: \n Item \t Price \t Quantity \t Total\n" + - items.map( - (item) => - `${item.title.padEnd(' ', 20)} \t $${item.price.toFixed(2)} \t ${ - item.quantity - } \t $${(item.price * item.quantity).toFixed(2)}` - ).join("\n"); - - console.log(text); + const text = listToText(); element.setAttribute( "href", @@ -302,9 +307,16 @@ function ShoppingList() { Download List {/* Share Via Email */} - + + + );