diff --git a/client/components/App.jsx b/client/components/App.jsx
index 73b357e..1a66ebf 100644
--- a/client/components/App.jsx
+++ b/client/components/App.jsx
@@ -1,9 +1,20 @@
-import React from 'react';
+import React, { useState } from 'react';
+import { BrowserRouter as Router, Routes, Route, useNavigate } from 'react-router-dom';
import FeatureContainer from '../containers/FeaturePageContainer';
+import Login from './Login';
+
function App() {
+ const [globalUser, setGlobalUser] = useState('');
return (
-
+
+
+
+ } />
+ : null} />
+
+
+
);
}
diff --git a/client/components/DropDown.jsx b/client/components/DropDown.jsx
index d4f4b8c..1e49ac9 100644
--- a/client/components/DropDown.jsx
+++ b/client/components/DropDown.jsx
@@ -5,7 +5,8 @@ function DropDown(props) {
const { ailment, handleChange, handleClick } = props;
return (
-
+
+
+
);
}
diff --git a/client/components/Login.jsx b/client/components/Login.jsx
new file mode 100644
index 0000000..9be181e
--- /dev/null
+++ b/client/components/Login.jsx
@@ -0,0 +1,110 @@
+import React, { useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+
+function Login(props) {
+ const { globalUser, setGlobalUser } = props;
+
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const navigate = useNavigate();
+
+ const handleChange = (event) => {
+ console.log('username before click', username);
+ if (event.target.id === 'username') setUsername(event.target.value);
+ if (event.target.id === 'password') setPassword(event.target.value);
+ };
+
+ const handleSignup = () => {
+ fetch('http://localhost:3000/signup', {
+ method: 'POST',
+ body: JSON.stringify({
+ username,
+ password,
+ }),
+ headers: { 'Content-Type': 'application/json' },
+ })
+ .then((res) => res.json())
+ .then((data) => {
+ console.log('user created:', data);
+ })
+ .catch((err) => console.log(err));
+ };
+
+ const handleLogin = () => {
+ fetch('http://localhost:3000/login', {
+ method: 'POST',
+ body: JSON.stringify({
+ username,
+ password,
+ }),
+ headers: { 'Content-Type': 'application/json' },
+ })
+ .then((res) => {
+ if (!res.ok) {
+ throw new Error(`Error! status: ${res.status}`);
+ }
+ return res.json();
+ })
+ .then((data) => {
+ console.log('username', data);
+ setGlobalUser(data);
+ navigate('/feature');
+ })
+ .catch((err) => console.log('login error:', err));
+ };
+
+ function handleSubmit(e) {
+ e.preventDefault();
+ e.target.reset();
+ }
+
+ return (
+
+
+
+
+
+ );
+}
+export default Login;
diff --git a/client/components/Table.jsx b/client/components/Table.jsx
index b417468..84e3606 100644
--- a/client/components/Table.jsx
+++ b/client/components/Table.jsx
@@ -12,18 +12,31 @@ function Table(props) {
);
-
+ // row is the particular food object
+ // add name
+ // iterate over nutrients
+ // add a cell for each nutrient
+ // wrap quantity+unit in a td tag
const tableBody = rows.map((row) => (
- {dataProperties.map((property) =>
- // if (column === 'linkedinValue') {
- // return | {row[column]} | ;
- // }
- // if (column === 'lastConnectionValue' || column === 'nextConnectionValue') {
- // const date = new Date(row[column]);
- // return {date.toDateString()} | ;
- // }
- {row[property]} | )}
+
+ {[{row.name} | , ...row.nutrients.map((property) => {
+ console.log('property', property);
+ if (!property) {
+ return N/A | ;
+ }
+ return {`${property.quantity ? property.quantity.toFixed(2) : 0} ${property.unit}`} | ;
+ },
+
+ // if (column === 'linkedinValue') {
+ // return {row[column]} | ;
+ // }
+ // if (column === 'lastConnectionValue' || column === 'nextConnectionValue') {
+ // const date = new Date(row[column]);
+ // return {date.toDateString()} | ;
+ // }
+ ),
+ ]}
));
diff --git a/client/containers/FeaturePageContainer.jsx b/client/containers/FeaturePageContainer.jsx
index bfc71b8..18b71c6 100644
--- a/client/containers/FeaturePageContainer.jsx
+++ b/client/containers/FeaturePageContainer.jsx
@@ -1,11 +1,38 @@
import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
import Table from '../components/Table';
import DropDown from '../components/DropDown';
-function FeatureContainer() {
- const tableHeaders = ['Insert', 'food', 'headers'];
- const tableProperties = ['Insert', 'food', 'properties'];
+function FeatureContainer(props) {
+ // const tableProperties = ['Insert', 'food', 'properties'];
const [ailment, setAilment] = React.useState('headache');
+ const navigate = useNavigate();
+
+ const tableProperties = [
+ 'CA',
+ 'K',
+ 'FE',
+ 'ZN',
+ 'VITA_RAE',
+ 'VITC',
+ 'VITB12',
+ 'VITD',
+ 'TOCPHA',
+ 'NIA',
+ ];
+ const tableHeaders = [
+ 'Name',
+ 'Calcium',
+ 'Potassium',
+ 'Iron',
+ 'Zinc',
+ 'Vitamin A',
+ 'Vitamin C',
+ 'Vitamin B-12',
+ 'Vitamin D',
+ 'Vitamin E',
+ 'Niacin',
+ ];
const [foodEntries, setFoodEntries] = useState([]);
@@ -13,22 +40,60 @@ function FeatureContainer() {
setAilment(event.target.value);
};
+ const handleLogout = () => {
+ props.setGlobalUser('');
+ navigate('/');
+ }
+
const handleClick = () => {
+ console.log('ailment', ailment);
fetch('http://localhost:3000/search', {
method: 'POST',
+ body: JSON.stringify({ ailment }),
+ headers: { 'Content-Type': 'application/json' },
})
.then((res) => res.json())
.then((data) => {
- setFoodEntries(data);
+ // parse the data that has been sent back
+ const parsedData = [];
+ data.forEach((food) => {
+ const foodRow = {
+ name: food.ingredients[0].text,
+ nutrients: [],
+ };
+ tableProperties.forEach((prop) => {
+ foodRow.nutrients.push(food.totalNutrients[prop]);
+ });
+ parsedData.push(foodRow);
+ });
+ console.log('parsedData', parsedData);
+ setFoodEntries(parsedData);
})
.catch((err) => console.log(err));
};
return (
<>
-
Placeholder
-
-
+
+
+
+
>
);
}
diff --git a/client/index.js b/client/index.js
index 461de4b..c95dd5b 100644
--- a/client/index.js
+++ b/client/index.js
@@ -1,14 +1,9 @@
import React from 'react';
-import { createRoot } from 'react-dom/client';
-import { BrowserRouter } from 'react-router-dom';
-import App from './components/App.jsx';
-// import styles from './scss/application.scss';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
+import App from './components/App';
-const domNode = document.getElementById('app');
-const root = createRoot(domNode);
-root.render(
-
-
-
-);
+import styles from './scss/application.scss';
+
+ReactDOM.createRoot(document.getElementById('app')).render(
);
diff --git a/client/scss/_variables.scss b/client/scss/_variables.scss
new file mode 100644
index 0000000..e69de29
diff --git a/client/scss/application.scss b/client/scss/application.scss
new file mode 100644
index 0000000..a47136a
--- /dev/null
+++ b/client/scss/application.scss
@@ -0,0 +1,166 @@
+@import '_variables';
+@import url('https://fonts.googleapis.com/css2?family=Lora&family=Sen&display=swap');
+
+// Colors
+
+$primaryBlack: #050505;
+$secondaryBlack: #5d5d5d;
+
+$primaryLime: #00ff00;
+$secondaryLime: #acffac;
+$lightestLime: #e2ffe2;
+
+$primarySalmon: salmon;
+$secondarySalmon: #fcc0b9;
+$lightestSalmon: #fdebe9;
+
+// Global
+body {
+ height: 100vh;
+ margin: 0px;
+ font-family: 'Sen', sans-serif;
+}
+.loginWrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ background-color: $lightestLime;
+ height: 100vh;
+}
+
+button {
+ background-color: $primarySalmon;
+ border: 1px solid white;
+ height: 30px;
+}
+
+#button :hover {
+ cursor: help;
+}
+
+// 1b. Login page
+
+
+.loginContents {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ h1{
+ margin-top: 100px;
+ }
+}
+
+
+.loginContainer {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid $secondarySalmon;
+ border-radius: 5%;
+ width: 300px;
+ box-shadow: 8px 10px 14px 0px rgb(128, 128, 128, 0.1);
+ background-color: white;
+ margin-top: 50px;
+}
+
+.loginContainer input {
+ margin-left: 10px;
+ margin-bottom: 20px;
+ border-radius: 5%;
+}
+
+.loginContainer button {
+ width: 200px;
+ margin-bottom: 20px;
+ border-radius: 5%;
+}
+
+// 2. Feature Page
+.feature-container {
+ margin-top: 100px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ border-radius: 2%;
+ margin-left: 5px;
+ margin-right: 5px;
+}
+
+//navbar
+nav {
+ display: flex;
+ justify-content: space-between;
+ border-bottom: 1px solid $secondarySalmon;
+}
+
+.logoutContainer {
+ display: flex;
+ align-items: center;
+ justify-content: end;
+ gap: 50px;
+}
+
+//dropdown
+
+.dropdown-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ width: 100vw;
+ background-color: $lightestLime;
+}
+
+.dropdown {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid $secondarySalmon;
+ border-radius: 5%;
+ width: 300px;
+ box-shadow: 8px 10px 14px 0px rgb(128, 128, 128, 0.1);
+ background-color: white;
+ margin-top: 100px;
+ margin-bottom: 100px;
+}
+
+.dropdown label {
+ padding: 20px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+}
+
+.dropdown select {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+table {
+ margin: 5px;
+ margin-top: 50px;
+}
+
+thead {
+ background-color: $lightestSalmon;
+ font-size: 14px;
+}
+
+tbody {
+ background-color: white;
+ font-size: 12px;
+}
+
+td {
+ margin-left: 20px;
+}
+
+tr {
+ padding: 10px;
+}
diff --git a/package.json b/package.json
index 8729a5c..7645b64 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
"main": "webpack.config.js",
"scripts": {
"start": "nodemon server/server.js",
- "dev": "webpack serve --config ./webpack.config.js --mode development",
+ "build": "webpack",
+ "dev": "webpack serve --config ./webpack.config.js --mode development & nodemon server/server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"eslintConfig": {
diff --git a/server/controllers/foodController.js b/server/controllers/foodController.js
index 632608a..148776b 100644
--- a/server/controllers/foodController.js
+++ b/server/controllers/foodController.js
@@ -16,8 +16,9 @@ const preciseURL =
// gets
foodController.getFoods = (req, res, next) => {
// queries mongoDB for illness, saves related foods in res locals
+ console.log('hitting get foods');
try {
- Illness.findOne({ illness: req.body }).then((data) => {
+ Illness.findOne({ ailment: req.body.ailment }).then((data) => {
res.locals.foods = data.foods;
return next();
});
@@ -31,6 +32,7 @@ foodController.getFoods = (req, res, next) => {
};
foodController.getFacts = async (req, res, next) => {
+ console.log('hitting get facts');
res.locals.facts = [];
try {
for (let food of res.locals.foods) {
diff --git a/server/controllers/userController.js b/server/controllers/userController.js
new file mode 100644
index 0000000..f00254e
--- /dev/null
+++ b/server/controllers/userController.js
@@ -0,0 +1,127 @@
+const User = require('../models/userModel');
+const bcrypt = require('bcrypt');
+const userController = {};
+
+userController.createUser = async (req, res, next) => {
+ const { username, password } = req.body;
+
+ try {
+ //check first to see if user is already created
+ const found = await User.findOne({ username });
+ if (found) {
+ console.log('yes user is created');
+ throw new Error('username is already in use');
+ }
+ const newUser = await new User({ username, password });
+ await newUser.save();
+ res.locals.user = newUser;
+ return next();
+ } catch (error) {
+ return next({
+ log: 'Error in userController.createUser middleware function',
+ status: 500,
+ message: { err: error.message },
+ });
+ }
+};
+
+userController.verifyUser = async (req, res, next) => {
+ const { username, password } = req.body;
+ try {
+ //grab encrypted password from db
+ const user = await User.find({ username });
+ console.log(user);
+ if (!user[0]) {
+ console.log('no user found');
+ throw new Error('no user found');
+ }
+ const matched = await bcrypt.compare(password, user[0].password);
+ if (!matched) {
+ throw new Error('password incorrect');
+ }
+ res.locals.username = username;
+ return next();
+ } catch (error) {
+ return next({
+ log: 'Error in userController.verifyUser middleware function',
+ status: 500,
+ message: { err: error.message },
+ });
+ }
+};
+
+userController.addFavorite = async (req, res, next) => {
+ const username = req.params.username;
+ try {
+ const user = await User.findOne({ username });
+ const favorite = user?.favorite;
+ if (!user) {
+ throw Error('user not found');
+ }
+ //const favorite = user.favorite;
+ //findoneandupdate {username}, {favorite:[...favorite,food]}
+ const addFavorite = await User.findOneAndUpdate(
+ { username },
+ { favorite: [...favorite, req.body] }
+ );
+ if (!addFavorite) {
+ throw Error('user cannot be updated');
+ }
+ res.locals.favorite = addFavorite;
+ return next();
+ } catch (error) {
+ return next({
+ log: 'Error in userController.addFavorite middleware function',
+ status: 500,
+ message: { err: error.message },
+ });
+ }
+ //findOne => return the user
+};
+
+userController.getFavorite = async (req, res, next) => {
+ const username = req.params.username;
+ try {
+ const user = await User.findOne({ username });
+ res.locals.favorite = user.favorite;
+ next();
+ } catch (error) {
+ return next({
+ log: 'Error in userController.getFavorites middleware function',
+ status: 500,
+ message: { err: error.message },
+ });
+ }
+};
+
+userController.deleteFavorite = async (req, res, next) => {
+ const username = req.params.username;
+ const { food } = req.body;
+ try {
+ const user = await User.findOne({ username });
+ const favorite = user?.favorite;
+ if (!user) {
+ throw Error('user not found');
+ }
+ //const favorite = user.favorite;
+ //findoneandupdate {username}, {favorite:[...favorite,food]}
+ const deleteFavorite = await User.findOneAndUpdate(
+ { username },
+ { favorite: favorite.filter((obj) => obj.food !== food) }
+ );
+ if (!deleteFavorite) {
+ throw Error('user cannot be updated');
+ }
+ res.locals.favorite = deleteFavorite;
+ return next();
+ } catch (error) {
+ return next({
+ log: 'Error in userController.addFavorite middleware function',
+ status: 500,
+ message: { err: error.message },
+ });
+ }
+ //findOne => return the user
+};
+
+module.exports = userController;
diff --git a/server/models/userModel.js b/server/models/userModel.js
new file mode 100644
index 0000000..20fb316
--- /dev/null
+++ b/server/models/userModel.js
@@ -0,0 +1,37 @@
+const mongoose = require('mongoose');
+const bcrypt = require('bcrypt');
+const SALT_WORK_FACTOR = 10;
+
+const userSchema = new mongoose.Schema({
+ username: {
+ type: String,
+ required: true,
+ unique: true,
+ },
+ password: {
+ type: String,
+ required: true,
+ },
+ favorite: {
+ type: Array,
+ default: [],
+ },
+});
+
+userSchema.pre('save', async function (next) {
+ try {
+ // "modification" includes creating a new pw, per mongoose
+ if (!this.isModified('password')) {
+ return next();
+ }
+ const hashedPassword = await bcrypt.hash(this.password, SALT_WORK_FACTOR);
+ this.password = hashedPassword;
+ return next();
+ } catch (err) {
+ return next(err);
+ }
+});
+
+const User = mongoose.model('User', userSchema);
+
+module.exports = User;
diff --git a/server/server.js b/server/server.js
index 51bd91e..4003d65 100644
--- a/server/server.js
+++ b/server/server.js
@@ -12,6 +12,8 @@ const PORT = 3000;
const foodController = require('./controllers/foodController');
+const userController = require('./controllers/userController');
+
//connect to database
const mongoURI =
'mongodb+srv://goblinshark:codesmith@foodremedy.nl2qzoj.mongodb.net/?retryWrites=true&w=majority';
@@ -26,14 +28,10 @@ mongoose
// needed to fix fetching problem in react
app.use(cors());
+app.use(express.json());
app.use(express.static(path.resolve(__dirname, '../client')));
-// listens, confirms connection
-app.listen(PORT, () => {
- console.log(`Success! Your application is running on port ${PORT}.`);
-});
-
// handles POST requests from illness dropdown
app.post(
'/search',
@@ -42,6 +40,33 @@ app.post(
(req, res) => res.status(200).send(res.locals.facts)
);
+//route for signing up
+app.get('/signup', (req, res) => {
+ res.sendFile(path.resolve(__dirname, '../client/signup.html'));
+});
+
+app.post('/signup', userController.createUser, (req, res) => {
+ res.status(200).json(res.locals.user);
+});
+
+app.post('/login', userController.verifyUser, (req, res) => {
+ res.status(200).json(res.locals.username);
+});
+
+//save favorite food to user's favorite folder
+app.patch('/user/addfav/:username',userController.addFavorite,(req, res)=> {
+ res.status(200).json(res.locals.favorite);
+})
+//get a collection of favorite food for a user
+app.get('/user/:username',userController.getFavorite,(req,res)=>{
+ res.status(200).json(res.locals.favorite);
+})
+
+//delete a favorite food from a user's favorite collection
+app.patch('/user/deletefav/:username',userController.deleteFavorite,(req, res)=> {
+ res.status(200).json(res.locals.favorite);
+})
+
// global error handler
app.use((err, req, res, next) => {
const defaultErr = {
@@ -53,3 +78,8 @@ app.use((err, req, res, next) => {
console.log(errorObj.log);
return res.status(errorObj.status).json(errorObj.message);
});
+
+// listens, confirms connection
+app.listen(PORT, () => {
+ console.log(`Success! Your application is running on port ${PORT}.`);
+});