├── README.md ├── .gitignore ├── .babelrc ├── client ├── assets │ ├── imports.scss │ └── styles.scss ├── app │ └── store.js ├── components │ ├── FridgeNav.jsx │ ├── SavedRecipe.jsx │ ├── Recipe.jsx │ ├── Recipes.jsx │ ├── Food.jsx │ └── Fridge.jsx ├── index.js ├── index.html ├── App.jsx ├── containers │ ├── FridgePage.jsx │ ├── NavBar.jsx │ ├── SignUp.jsx │ └── Login.jsx └── features │ ├── recipeSlice.js │ ├── fridgeSlice.js │ └── userSlice.js ├── server ├── routes │ ├── userRouter.js │ ├── fridgeRouter.js │ └── recipeRouter.js ├── models │ └── dbModel.js ├── server.js └── controllers │ ├── recipeController.js │ ├── userController.js │ └── fridgeController.js ├── LICENSE ├── webpack.config.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # FridgeTime -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | 4 | #local env files 5 | .env -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /client/assets/imports.scss: -------------------------------------------------------------------------------- 1 | $base-color: rgb(159, 194, 204); 2 | $button-color: darken($base-color, 30%); 3 | $lighter-color: lighten($base-color, 50%); 4 | $header-color: lighten($base-color, 25%); -------------------------------------------------------------------------------- /client/app/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import fridgeReducer from '../features/fridgeSlice'; 3 | import userReducer from '../features/userSlice'; 4 | import recipeReducer from '../features/recipeSlice'; 5 | 6 | export const store = configureStore({ 7 | reducer: { 8 | fridge: fridgeReducer, 9 | user: userReducer, 10 | recipes: recipeReducer, 11 | }, 12 | }) -------------------------------------------------------------------------------- /client/components/FridgeNav.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { Link } from 'react-router-dom' 4 | 5 | 6 | const FridgeNav = () => { 7 | return ( 8 |
9 | 10 | 11 |
12 | ) 13 | }; 14 | 15 | export default FridgeNav; -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './assets/styles.scss' 4 | import App from './App.jsx' 5 | import { store } from './app/store' 6 | import { Provider } from 'react-redux' 7 | import { BrowserRouter } from 'react-router-dom' 8 | 9 | ReactDOM.render( 10 | 11 | 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ) -------------------------------------------------------------------------------- /server/routes/userRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const userController = require('../controllers/userController'); 3 | 4 | const userRouter = express.Router(); 5 | 6 | userRouter.post('/signup', 7 | userController.signup, 8 | (req, res) => { 9 | res.status(201).json(res.locals.newUser) 10 | } 11 | ); 12 | 13 | userRouter.post('/login', 14 | userController.login, 15 | (req, res) => { 16 | console.log('I am in the userRouter') 17 | res.status(200).json(res.locals.user); 18 | } 19 | ); 20 | 21 | 22 | module.exports = userRouter; -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Fridge Time 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /server/routes/fridgeRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const fridgeController = require('../controllers/fridgeController'); 3 | 4 | const fridgeRouter = express.Router(); 5 | 6 | fridgeRouter.get('/:id', 7 | fridgeController.getFridge, 8 | (req, res) => { 9 | console.log('I am in the GET fridgeRouter') 10 | res.status(200).json(res.locals.fridge); 11 | } 12 | ); 13 | 14 | fridgeRouter.post('/', 15 | fridgeController.addIngredient, 16 | (req, res) => { 17 | // console.log('I am in the POST fridgeRouter') 18 | res.status(200).json(res.locals.ingredient); 19 | } 20 | ); 21 | 22 | module.exports = fridgeRouter; -------------------------------------------------------------------------------- /server/routes/recipeRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const recipeController = require('../controllers/recipeController'); 3 | 4 | const recipeRouter = express.Router(); 5 | 6 | recipeRouter.get('/:id', 7 | recipeController.getRecipes, 8 | (req, res) => { 9 | console.log('I am in GET recipeRouter') 10 | res.status(200).json(res.locals.recipes); 11 | } 12 | ); 13 | 14 | recipeRouter.post('/', 15 | recipeController.saveRecipe, 16 | (req, res) => { 17 | res.status(201).json(res.locals.recipe) 18 | } 19 | ); 20 | 21 | recipeRouter.delete('/', 22 | recipeController.deleteRecipe, 23 | (req, res) => { 24 | res.status(201).json(res.locals.recipe) 25 | } 26 | ); 27 | 28 | module.exports = recipeRouter; -------------------------------------------------------------------------------- /client/components/SavedRecipe.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { deleteRecipe, selectSaved } from '../features/recipeSlice'; 5 | 6 | const SavedRecipe = (props) => { 7 | const { saved } = props; 8 | const savedRecipes = useSelector(selectSaved) 9 | const dispatch = useDispatch(); 10 | 11 | const deleteSaved = () => { 12 | dispatch(deleteRecipe(saved)) 13 | console.log(savedRecipes) 14 | }; 15 | 16 | return ( 17 |
18 |

{saved}

19 | 20 |
21 | ) 22 | }; 23 | 24 | export default SavedRecipe; -------------------------------------------------------------------------------- /server/models/dbModel.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv'); 2 | const { Pool } = require('pg'); 3 | dotenv.config(); 4 | 5 | const PG_URI = process.env.POSTGRESS_URI; 6 | // console.log('I am PG_URI', PG_URI) 7 | 8 | // create a new pool here using the connection string above 9 | const pool = new Pool({ 10 | connectionString: PG_URI 11 | }); 12 | 13 | 14 | // We export an object that contains a property called query, 15 | // which is a function that returns the invocation of pool.query() after logging the query 16 | // This will be required in the controllers to be the access point to the database 17 | module.exports = { 18 | query: (text, params, callback) => { 19 | console.log('executed query', text); 20 | return pool.query(text, params, callback); 21 | } 22 | }; -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Route, Routes, Link } from 'react-router-dom'; 4 | import Login from './containers/Login.jsx'; 5 | import NavBar from './containers/NavBar.jsx'; 6 | import SignUp from './containers/SignUp.jsx'; 7 | import FridgePage from './containers/FridgePage.jsx'; 8 | 9 | 10 | const App = () => { 11 | return ( 12 |
13 | 14 |
15 | 16 | } /> 17 | } /> 18 | } /> 19 | 20 |
21 |
22 | ); 23 | }; 24 | 25 | export default App; -------------------------------------------------------------------------------- /client/containers/FridgePage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { Route, Routes, Link } from 'react-router-dom'; 4 | import FridgeNav from '../components/FridgeNav.jsx' 5 | import Fridge from '../components/Fridge.jsx' 6 | import Recipes from '../components/Recipes.jsx' 7 | 8 | 9 | const FridgePage = () => { 10 | return ( 11 |
12 |
13 | 14 |
15 |
16 | 17 | } /> 18 | } /> 19 | 20 |
21 |
22 | ) 23 | }; 24 | 25 | export default FridgePage; -------------------------------------------------------------------------------- /client/components/Recipe.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import axios from 'axios'; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import { addRecipe, selectSaved } from '../features/recipeSlice'; 6 | import { selectUserId } from '../features/userSlice'; 7 | 8 | const Recipe = (props) => { 9 | const { recipe } = props; 10 | const userId = useSelector(selectUserId); 11 | const dispatch = useDispatch(); 12 | 13 | const handleRecipe = () => { 14 | dispatch(addRecipe(recipe)) 15 | axios.post('api/recipe/addRecipe', { user_id: userId, recipe }) 16 | .then(res => console.log('submit successful')) 17 | .catch(err => console.log(err)) 18 | }; 19 | 20 | return ( 21 |
22 |

{recipe}

23 | 24 |
25 | ) 26 | }; 27 | 28 | export default Recipe; -------------------------------------------------------------------------------- /client/components/Recipes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { useEffect } from 'react'; 4 | import axios from 'axios'; 5 | import { useDispatch, useSelector } from 'react-redux'; 6 | import { selectSaved } from '../features/recipeSlice'; 7 | import { selectUserId } from '../features/userSlice'; 8 | import SavedRecipe from './SavedRecipe.jsx'; 9 | 10 | 11 | const Recipes = () => { 12 | const savedRecipes = useSelector(selectSaved); 13 | const dispatch = useDispatch(); 14 | const recipeUserId = useSelector(selectUserId) 15 | 16 | 17 | useEffect(() => { 18 | axios.get(`api/recipes/getrecipes/${recipeUserId}`) 19 | .then(res => res.data) 20 | }, []) 21 | 22 | return ( 23 |
24 |

Saved recipes...

25 | {savedRecipes.map((saved, i) => )} 26 |
27 | ) 28 | }; 29 | 30 | export default Recipes; -------------------------------------------------------------------------------- /client/features/recipeSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | export const recipeSlice = createSlice({ 4 | name: 'recipes', 5 | initialState: { 6 | recipeArray: [], 7 | savedRecipes: [], 8 | }, 9 | reducers: { 10 | returnedRecipes: (state, action) => { 11 | console.log(action.payload) 12 | state.recipeArray.push(...action.payload); 13 | }, 14 | addRecipe: (state, action) => { 15 | if (!state.savedRecipes.includes(action.payload)) { 16 | state.savedRecipes.push(action.payload) 17 | } 18 | }, 19 | deleteRecipe: (state, action) => { 20 | state.savedRecipes = state.savedRecipes.filter(item => item !== action.payload) 21 | } 22 | } 23 | }) 24 | 25 | export const { returnedRecipes, addRecipe, deleteRecipe } = recipeSlice.actions; 26 | export const selectRecipes = (state) => state.recipes.recipeArray; 27 | export const selectSaved = (state) => state.recipes.savedRecipes; 28 | 29 | export default recipeSlice.reducer; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ReinforcementPikachu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/components/Food.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { useState } from 'react' 4 | import { useDispatch } from 'react-redux'; 5 | import { addIngredient, deleteItem } from '../features/fridgeSlice'; 6 | 7 | const Food = (props) => { 8 | const { food } = props; 9 | const dispatch = useDispatch(); 10 | const [toggle, setToggle] = useState(false) 11 | 12 | const foodClicked = () => { 13 | setToggle(prevState => !prevState) 14 | } 15 | 16 | const handleChange = (event) => { 17 | if (event.target.checked) { 18 | dispatch(addIngredient(event.target.value)); 19 | } 20 | } 21 | 22 | const deleteFood = () => { 23 | dispatch(deleteItem(food)) 24 | } 25 | 26 | return ( 27 |
28 | handleChange(e)}/> 29 | 30 |
31 | {toggle && ()} 32 |
33 | ) 34 | }; 35 | 36 | export default Food; -------------------------------------------------------------------------------- /client/containers/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Link, Navigate } from 'react-router-dom'; 4 | import { useSelector, useDispatch } from 'react-redux'; 5 | import { selectAuthenticated, logOut } from '../features/userSlice'; 6 | 7 | const NavBar = () => { 8 | const authenticated = useSelector(selectAuthenticated); 9 | const dispatch = useDispatch(); 10 | 11 | const logOutHandler = () => { 12 | dispatch(logOut()) 13 | } 14 | 15 | return ( 16 |
17 |

Oh No? No! Oh, Wow!

18 |
19 | { authenticated 20 | ? 21 | : 22 | } 23 | { authenticated 24 | ? 25 | : 26 | } 27 | {/* */} 28 |
29 |
30 | ) 31 | } 32 | 33 | export default NavBar; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | mode: process.env.NODE_ENV, 6 | plugins: [ 7 | new HtmlWebpackPlugin({ 8 | template: path.join(__dirname, './client/index.html'), 9 | filename: 'index.html', 10 | })], 11 | entry: './client/index.js', 12 | output: { 13 | path: path.resolve(__dirname, 'build'), 14 | publicPath: '/', 15 | filename: 'bundle.js', 16 | }, 17 | module: { 18 | rules: [{ 19 | test: /\.jsx?/, 20 | exclude: /(node_modules)/, 21 | use: { 22 | loader: 'babel-loader', 23 | options: { 24 | presets: ['@babel/preset-env', '@babel/preset-react'] 25 | } 26 | } 27 | }, { 28 | test: /\.s[ac]ss$/i, 29 | use: [ 30 | 'style-loader', 31 | 'css-loader', 32 | 'sass-loader' 33 | ] 34 | }] 35 | }, 36 | devServer: { 37 | proxy: { 38 | "/build": "http://localhost:3000", 39 | "/api": "http://localhost:3000", 40 | }, 41 | compress: true, 42 | port: 8080, 43 | historyApiFallback: true 44 | } 45 | }; -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { appendFile } = require('fs'); 3 | const path = require('path'); 4 | 5 | const userRouter = require('./routes/userRouter'); 6 | const fridgeRouter = require('./routes/fridgeRouter'); 7 | const recipeRouter = require('./routes/recipeRouter'); 8 | 9 | const PORT = 3000; 10 | 11 | const app = express(); 12 | 13 | app.use(express.json()); 14 | app.use(express.urlencoded({extended: true})); 15 | 16 | app.use('/api/user', userRouter) 17 | app.use('/api/fridge', fridgeRouter) 18 | app.use('/api/recipe', recipeRouter) 19 | 20 | //serve HTML 21 | app.get('/', (req, res) => { 22 | return res.status(200).sendFile(path.resolve(__dirname, '../client/index.html')); 23 | }); 24 | 25 | //serve static build files 26 | app.use('/build', express.static(path.join(__dirname, '../build'))); 27 | 28 | //error 404 handler 29 | app.use((req, res) => { 30 | res.status(404).send('Sorry, page not found >.<') 31 | }); 32 | 33 | //global error 34 | app.use((err, res) => { 35 | const error = { 36 | log: 'Express error handler caught unknown middleware error', 37 | status: 400, 38 | message: { err: 'An error occurred' } 39 | }; 40 | const errorObj = Object.assign({}, error, err); 41 | console.log(errorObj.log); 42 | return res.status(errorObj.status).json(errorObj.message); 43 | }); 44 | 45 | 46 | 47 | app.listen(PORT, () => console.log(`Server listening on port: ${PORT}`)); 48 | 49 | module.exports = app; -------------------------------------------------------------------------------- /client/features/fridgeSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | export const fridgeSlice = createSlice({ 4 | name: 'fridge', 5 | initialState: { 6 | food: '', 7 | fridgeList: [], 8 | ingredientList: [] 9 | }, 10 | reducers: { 11 | addItem: (state, action) => { 12 | if (typeof action.payload === 'string') { 13 | state.fridgeList.push(action.payload) 14 | } else { 15 | state.fridgeList.push(...action.payload); 16 | } 17 | }, 18 | deleteItem: (state, action) => { 19 | state.fridgeList = state.fridgeList.filter(item => item !== action.payload) 20 | }, 21 | 22 | createFood: (state, action) => { 23 | state.food = action.payload 24 | }, 25 | addIngredient: (state, action) => { 26 | if (!state.ingredientList.includes(action.payload)) { 27 | state.ingredientList.push(action.payload) 28 | } 29 | }, 30 | clearIngredients: (state, action) => { 31 | state.ingredientList = []; 32 | } 33 | }, 34 | }); 35 | 36 | export const { addItem, deleteItem, createFood, addIngredient, clearIngredients } = fridgeSlice.actions; 37 | 38 | export const selectFood = (state) => state.fridge.food; 39 | export const selectContents = (state) => state.fridge.fridgeList; 40 | export const selectIngredients = (state) => state.fridge.ingredientList; 41 | 42 | export default fridgeSlice.reducer; -------------------------------------------------------------------------------- /client/features/userSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | export const userSlice = createSlice({ 4 | name: 'user', 5 | initialState: { 6 | userId: 0, 7 | username: '', 8 | authenticated: false, 9 | error: null, 10 | signup: null 11 | }, 12 | reducers: { 13 | logIn: (state, action) => { 14 | console.log(action) 15 | state.userId = action.payload.user_id, 16 | state.username = action.payload.username, 17 | state.authenticated = true 18 | state.error = false 19 | }, 20 | logOut: (state, action) => { 21 | state.userId = 0, 22 | state.username = '', 23 | state.authenticated = false 24 | }, 25 | noUser: (state) => { 26 | state.error = true, 27 | state.authenticated = false 28 | }, 29 | newUser: (state) => { 30 | state.signup = true; 31 | }, 32 | userInDatabase: (state,action) => { 33 | state.error = true 34 | } 35 | }, 36 | }); 37 | 38 | export const {logIn, logOut, noUser, newUser, userInDatabase} = userSlice.actions; 39 | 40 | export const selectUserId = (state) => state.user.userId; 41 | export const selectUsername = (state) => state.user.username; 42 | export const selectAuthenticated = (state) => state.user.authenticated; 43 | export const selectError = (state) => state.user.error; 44 | export const selectSignup = (state) => state.user.signup 45 | 46 | export default userSlice.reducer; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fridgetime", 3 | "version": "1.0.0", 4 | "description": "fridge app", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "NODE_ENV=production node server/server.js", 8 | "build": "NODE_ENV=production webpack", 9 | "dev": "NODE_ENV=development node server/server.js & NODE_ENV=development webpack serve --open --hot", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ReinforcementPikachu/FridgeTime.git" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/ReinforcementPikachu/FridgeTime/issues" 20 | }, 21 | "homepage": "https://github.com/ReinforcementPikachu/FridgeTime#readme", 22 | "dependencies": { 23 | "@reduxjs/toolkit": "^1.9.1", 24 | "argon2": "^0.30.2", 25 | "axios": "^1.2.1", 26 | "dotenv": "^16.0.3", 27 | "express": "^4.18.2", 28 | "pg": "^8.8.0", 29 | "react": "^18.2.0", 30 | "react-dom": "^18.2.0", 31 | "react-redux": "^8.0.5", 32 | "react-router": "^6.4.4", 33 | "react-router-dom": "^6.4.4", 34 | "redux": "^4.2.0" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "^7.20.5", 38 | "@babel/preset-env": "^7.20.2", 39 | "@babel/preset-react": "^7.18.6", 40 | "babel-loader": "^9.1.0", 41 | "css-loader": "^6.7.2", 42 | "html-webpack-plugin": "^5.5.0", 43 | "sass": "^1.56.1", 44 | "sass-loader": "^13.2.0", 45 | "style-loader": "^3.3.1", 46 | "url-loader": "^4.1.1", 47 | "webpack": "^5.75.0", 48 | "webpack-cli": "^5.0.1", 49 | "webpack-dev-server": "^4.11.1" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /server/controllers/recipeController.js: -------------------------------------------------------------------------------- 1 | const db = require('../models/dbModel'); 2 | 3 | const recipeController = {}; 4 | 5 | //get saved recipes from db 6 | recipeController.getRecipes = async (req, res, next) => { 7 | console.log('i am getRecipes middleware'); 8 | const { id } = req.params; 9 | const query = `SELECT * from Recipe WHERE user_id = '${id}'`; 10 | try { 11 | const recipes = await db.query(query); 12 | console.log('recipes', recipes); 13 | res.locals.recipes = recipes; 14 | return next(); 15 | } 16 | catch (err) { 17 | return next({ 18 | log: `recipeController.js: ERROR: ${err}`, 19 | status: 500, 20 | message: { 21 | err: 'An error occurred in recipeController.getRecipes middleware', 22 | }, 23 | }); 24 | } 25 | }; 26 | 27 | recipeController.addRecipe = async (req, res, next) => { 28 | const name = req.body.recipe; 29 | console.log(req.body); 30 | const query = `INSERT INTO Recipes ('id' 'name') VALUES('${user_id}', '${name}')`; 31 | try{ 32 | const insertion = await db.query(query); 33 | console.log(insertion); 34 | res.locals.insertion = insertion; 35 | return next(); 36 | } catch(err){ 37 | return next({ 38 | log: `recipeController.js: ERROR: ${err}`, 39 | status: 500, 40 | message: { 41 | err: 'An error occurred in recipeController.addRecipe middleware' 42 | } 43 | }); 44 | } 45 | }; 46 | 47 | recipeController.deleteRecipe = async (req, res, next) => { 48 | const { user_id, name } = req.body; 49 | try { 50 | const deleteQuery = `DELETE FROM Recipe WHERE user_id=${user_id} AND name='${name}'`; 51 | await db.query(deleteQuery) 52 | res.locals.recipe = 'Recipe Deleted'; 53 | return next(); 54 | } 55 | catch (err) { 56 | return next({ 57 | log: `recipeController.js: ERROR: ${err}`, 58 | status: 400, 59 | message: { 60 | err: 'An error occurred in recipeController.deleteRecipe middleware', 61 | }, 62 | }); 63 | } 64 | }; 65 | 66 | module.exports = recipeController; -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const db = require('../models/dbModel'); 2 | const argon2 = require('argon2'); 3 | 4 | const userController = {}; 5 | 6 | userController.signup = async (req, res, next) => { 7 | console.log('i am signup middleware'); 8 | const { username, password } = req.body; 9 | //check if user exists 10 | //if exists send back 'username already exists" 11 | //else create user 12 | try { 13 | const hashedPassword = await argon2.hash(password); 14 | const query = `SELECT username from Users WHERE username = '${username}'`; 15 | const existingUsername = await db.query(query); 16 | console.log('i am existingUsername', existingUsername.rows); 17 | if (!existingUsername.rows.length) { 18 | const insert = `INSERT INTO Users (username, password) VALUES('${username}', '${password}')`; 19 | const idQuery = `select user_id from Users where username = '${username}'`; 20 | await db.query(insert); 21 | const userId = await db.query(idQuery); 22 | console.log(userId, 'userId'); 23 | res.locals.newUser = userId.rows[0] 24 | return next(); 25 | } else { 26 | res.locals.newUser = existingUsername.rows[0]; 27 | return next(); 28 | } 29 | } catch (err) { 30 | return next({ 31 | log: `userController.js: ERROR: ${err}`, 32 | status: 400, 33 | message: { 34 | err: 'An error occurred in userController.signup middleware' 35 | } 36 | }); 37 | } 38 | }; 39 | 40 | userController.login = async (req, res, next) => { 41 | console.log('i am login middleware'); 42 | const { username, password } = req.body; 43 | const hashedPassword = await argon2.hash(password); 44 | const query = `SELECT user_id, username from Users WHERE username = '${username}' and password = '${password}'`; 45 | try { 46 | const found = await db.query(query); 47 | //need to send back id and username 48 | console.log('i am found', found); 49 | res.locals.user = found.rows[0]; 50 | return next(); 51 | } catch (err) { 52 | return next({ 53 | log: `userController.js: ERROR: ${err}`, 54 | status: 400, 55 | message: { 56 | err: 'An error occurred in userController.login middleware' 57 | } 58 | }); 59 | } 60 | }; 61 | 62 | module.exports = userController; -------------------------------------------------------------------------------- /server/controllers/fridgeController.js: -------------------------------------------------------------------------------- 1 | const db = require('../models/dbModel'); 2 | const axios = require('axios') 3 | 4 | const fridgeController = {}; 5 | 6 | fridgeController.getFridge = async (req, res, next) => { 7 | console.log('i am getFridge middleware'); 8 | const { id } = req.params; 9 | //need to send back in array 10 | const query = `SELECT ingredient from Fridge WHERE user_id = '${id}'`; 11 | try { 12 | const ingredients = await db.query(query); 13 | const rows = ingredients.rows; 14 | const userIngredients = []; 15 | for (let i = 0; i < rows.length; i++) { 16 | userIngredients.push(rows[i].ingredient) 17 | } 18 | console.log('i am userIngredients', userIngredients) 19 | res.locals.fridge = userIngredients; 20 | return next(); 21 | } 22 | catch (err) { 23 | return next({ 24 | log: `fridgeController.js: ERROR: ${err}`, 25 | status: 500, 26 | message: { 27 | err: 'An error occurred in fridgeController.getFridge middleware', 28 | }, 29 | }); 30 | } 31 | }; 32 | 33 | fridgeController.addIngredient = async (req, res, next) => { 34 | // console.log('i am addIngredient middleware'); 35 | const { user_id, ingredient } = req.body; 36 | // console.log('i am ingredient', ingredient) 37 | try { 38 | const insertQuery = `INSERT INTO Fridge VALUES(${user_id}, DEFAULT, '${ingredient}')`; 39 | await db.query(insertQuery) 40 | res.locals.ingredient = 'Ingredient Added'; 41 | return next(); 42 | } 43 | catch (err) { 44 | return next({ 45 | log: `fridgeController.js: ERROR: ${err}`, 46 | status: 500, 47 | message: { 48 | err: 'An error occurred in fridgeController.addIngredient middleware', 49 | }, 50 | }); 51 | } 52 | }; 53 | 54 | module.exports = fridgeController; 55 | 56 | // console.log('i am addIngredient middleware'); 57 | // const { user_id, ingredients } = req.body; 58 | // console.log('i am ingredients', ingredients) 59 | 60 | // try { 61 | // for (let i = 0; i < ingredients.length; i++) { 62 | // const insertQuery = `INSERT INTO Fridge VALUES(${user_id}, DEFAULT, '${ingredients[i]}')` 63 | // await db.query(insertQuery) 64 | // console.log(ingredients[i], 'inside the for loop') 65 | // }; 66 | // const userFridge = await db.query(`SELECT ingredient from Fridge WHERE user_id = '${user_id}'`); 67 | // console.log('i am userFridge', userFridge.rows) 68 | // res.locals.ingredient = userFridge.rows[0].ingredient; 69 | // return next(); 70 | // } 71 | // catch (err) { 72 | // return next({ 73 | // log: `fridgeController.js: ERROR: ${err}`, 74 | // status: 400, 75 | // message: { 76 | // err: 'An error occurred in fridgeController.addIngredient middleware', 77 | // }, 78 | // }); 79 | // } -------------------------------------------------------------------------------- /client/containers/SignUp.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { useSelector, useDispatch } from 'react-redux'; 4 | import { logIn, noUser, selectUserId, selectUsername, selectAuthenticated, selectError } from '../features/userSlice'; 5 | import axios from 'axios'; 6 | import { Navigate, Link } from 'react-router-dom'; 7 | 8 | const SignUp = () => { 9 | const newId = useSelector(selectUserId); 10 | const newUsername = useSelector(selectUsername); 11 | const authenticated = useSelector(selectAuthenticated); 12 | const error = useSelector(selectError); 13 | const dispatch = useDispatch(); 14 | const signUpHandler = (event) => { 15 | event.preventDefault(); 16 | const username = document.getElementById('loginUsername').value; 17 | const pw = document.getElementById('loginPassword').value; 18 | // console.log('submitted id===>', username) 19 | // console.log('submitted pw===>', pw) 20 | // console.log(newUsername,'state of username before') 21 | // if (username==='chris' && pw ==='chris'){ 22 | // dispatch(logIn({id:1, username:username})) 23 | // } else { 24 | // alert('Looks like that username already exists. Please go back to login and try again') 25 | // console.log(newUsername,'state of username after') 26 | // console.log(newId, 'state of id after') 27 | axios.post('/api/user/signup', { 28 | username: username, 29 | password: pw 30 | }).then((res => { 31 | if (res.data){ 32 | console.log(res.data, 'signup res.data') 33 | dispatch(logIn({user_id:res.data.user_id.user_id, username: username})) 34 | } 35 | })).catch((error) => { 36 | alert('This username already exist, please try a different username or return to login page') 37 | } 38 | ) 39 | } 40 | // console.log(newUsername,'state of username after') 41 | // console.log(newId, 'state of id after') 42 | // axios.post('/api/user', { 43 | // username: username, 44 | // password: pw 45 | // }).then((res => { 46 | // if (res.data){ 47 | // dispatch(logIn(res.data)) 48 | // } 49 | // })).catch((error) => { 50 | // dispatch(noUser()) 51 | // } 52 | // ) 53 | return ( 54 |
55 |
56 |

Please Sign Up Here

57 | 63 | 64 |
65 | 71 | 72 |
73 |
74 | 75 | 76 | {authenticated && ( 77 | 78 | )} 79 |
80 | ) 81 | } 82 | export default SignUp; -------------------------------------------------------------------------------- /client/containers/Login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { useSelector, useDispatch } from 'react-redux'; 4 | import { logIn, noUser, selectUserId, selectUsername, selectAuthenticated, selectError } from '../features/userSlice'; 5 | import axios from 'axios'; 6 | import { Navigate } from 'react-router-dom'; 7 | 8 | 9 | const Login = () => { 10 | const newId = useSelector(selectUserId); 11 | const newUsername = useSelector(selectUsername); 12 | const authenticated = useSelector(selectAuthenticated); 13 | const error = useSelector(selectError); 14 | const dispatch = useDispatch(); 15 | 16 | 17 | const loginHandler = (event) => { 18 | event.preventDefault(); 19 | const username = document.getElementById('loginUsername').value; 20 | const pw = document.getElementById('loginPassword').value; 21 | console.log('submitted id===>', username) 22 | console.log('submitted pw===>', pw) 23 | // console.log(newUsername,'state of username before') 24 | // if (username==='chris' && pw ==='chris'){ 25 | // dispatch(logIn({id:1, username:username})) 26 | // } else { 27 | // alert('Oh no, your username or log in is not correct') 28 | // // dispatch(noUser()) 29 | // } 30 | // console.log(newUsername,'state of username after') 31 | // console.log(newId, 'state of id after') 32 | axios.post('/api/user/login', { 33 | username: username, 34 | password: pw 35 | }).then((res => { 36 | if (res.data){ 37 | console.log(res.data, 'res.data') 38 | dispatch(logIn(res.data)) 39 | } 40 | })).catch((error) => { 41 | alert('That username or password is incorrect or does not exist, please try again or go to our signup page') 42 | console.log(username, 'username') 43 | console.log(password, 'password') 44 | } 45 | ) 46 | } 47 | // function togglePassword() { 48 | // var x = document.getElementById("loginPassword"); 49 | // if (x.type === "password") { 50 | // x.type = "text"; 51 | // } else { 52 | // x.type = "password"; 53 | // } 54 | // } 55 | return ( 56 |
57 |
58 |

Please Log In

59 | 65 | 66 |

67 | 73 | 74 | {/*

75 | */} 76 |

77 |
78 | 79 | {authenticated && ( 80 | 81 | )} {error && ( 82 | 83 | )} 84 |
85 | ) 86 | }; 87 | 88 | export default Login; -------------------------------------------------------------------------------- /client/components/Fridge.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { useState, useRef} from 'react'; 3 | import ReactDOM from 'react-dom' 4 | import axios from 'axios'; 5 | import { useSelector, useDispatch } from 'react-redux'; 6 | import { addItem, deleteItem, createFood, selectFood, selectContents, selectIngredients, clearIngredients } from '../features/fridgeSlice' 7 | import { selectRecipes, returnedRecipes } from '../features/recipeSlice'; 8 | import Food from './Food.jsx'; 9 | import { selectUserId, selectUsername } from '../features/userSlice'; 10 | 11 | import Recipe from './Recipe.jsx'; 12 | 13 | 14 | const Fridge = () => { 15 | const newFood = useSelector(selectFood); 16 | const contents = useSelector(selectContents); 17 | const ingredients = useSelector(selectIngredients); 18 | const fridgeUserId = useSelector(selectUserId) 19 | const fridgeUsername = useSelector(selectUsername) 20 | const recipes = useSelector(selectRecipes); 21 | const dispatch = useDispatch(); 22 | const foodForm = useRef(null); 23 | const recipeForm = useRef(null) 24 | 25 | 26 | console.log('userId in fridge', fridgeUserId) 27 | console.log('username in fridge', fridgeUsername) 28 | 29 | useEffect(()=>{ 30 | axios.get(`/api/fridge/${fridgeUserId}`) 31 | .then(res => { 32 | dispatch(addItem(res.data)) 33 | }) 34 | .catch(err => console.log(err)) 35 | }, []) 36 | 37 | const addFood = (event) => { 38 | event.preventDefault(); 39 | dispatch(addItem(newFood)); 40 | axios.post('api/fridge', { user_id: fridgeUserId, ingredient: newFood }) 41 | .then(res => console.log ('request successful')) 42 | .catch(err => console.log(err)) 43 | foodForm.current.value = ''; 44 | } 45 | 46 | const getRecipes = (event) => { 47 | event.preventDefault(); 48 | const recipeArray = []; 49 | const recipeIdArray = []; 50 | const ingredientString = ingredients.map((food, i) => { 51 | if (i === 0) { 52 | return `${food}`; 53 | } 54 | else { 55 | return `,+${food}`; 56 | } 57 | }).join(''); 58 | console.log(ingredientString) 59 | axios.get(`https://api.spoonacular.com/recipes/findByIngredients?ingredients=${ingredientString}&apiKey=f275a7ebbc2f42d583a70642829cfe07`) 60 | .then(res => { 61 | console.log(res.data, 'res.data') 62 | res.data.forEach((element)=>{ 63 | recipeArray.push(element.title) 64 | recipeIdArray.push(element.id) 65 | }) 66 | dispatch(returnedRecipes(recipeArray)) 67 | }) 68 | .catch(err => console.log(err)) 69 | console.log(recipes, 'recipes') 70 | dispatch(clearIngredients()) 71 | recipeForm.current.reset(); 72 | } 73 | 74 | return ( 75 |
76 |
77 |
78 | {contents.map((food, i) => )} 79 | 80 |
81 |
82 |
83 | dispatch(createFood(e.target.value))}/> 89 |
90 | 91 | 92 |
93 |
94 | {recipes.map((recipe, i) => )} 95 |
96 |
97 | ) 98 | } 99 | 100 | export default Fridge; -------------------------------------------------------------------------------- /client/assets/styles.scss: -------------------------------------------------------------------------------- 1 | @import './imports.scss'; 2 | 3 | * { 4 | box-sizing: border-box; 5 | font-size: 2vh; 6 | font-family: 'Lora', serif; 7 | background-color: $base-color; 8 | border-color: $button-color; 9 | } 10 | 11 | button { 12 | border-radius: 5px; 13 | background-color: $button-color; 14 | box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px; 15 | 16 | } 17 | 18 | .login-wrapper { 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | h1 { 24 | text-align: center; 25 | margin-bottom: 25%; 26 | background-color: $lighter-color; 27 | } 28 | border: 1px solid $button-color; 29 | form { 30 | box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px; 31 | padding: 5%; 32 | margin: .5%; 33 | background-color: $lighter-color; 34 | input { 35 | background-color: $lighter-color; 36 | } 37 | } 38 | padding: 2%; 39 | button { 40 | padding: .5%; 41 | margin: .5% 42 | } 43 | } 44 | 45 | .container { 46 | display: grid; 47 | grid-template-columns: 1fr 1fr 1fr 1fr; 48 | grid-template-rows: 1fr 1fr 1fr 1fr; 49 | gap: 0px 0px; 50 | grid-auto-flow: row; 51 | grid-template-areas: 52 | "fridge-wrapper fridge-wrapper add-wrapper add-wrapper" 53 | "fridge-wrapper fridge-wrapper add-wrapper add-wrapper" 54 | "recipes-wrapper recipes-wrapper recipes-wrapper recipes-wrapper" 55 | "recipes-wrapper recipes-wrapper recipes-wrapper recipes-wrapper"; 56 | border: 1px solid black; 57 | border-radius: 5px; 58 | box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px; 59 | } 60 | 61 | .add-wrapper { 62 | grid-area: add-wrapper; 63 | display: flex; 64 | flex-direction: column; 65 | align-items: center; 66 | justify-content: center; 67 | padding: 10%; 68 | border: 1px solid $button-color; 69 | border-radius: 5px; 70 | button { 71 | margin-top: 1%; 72 | width: 150px; 73 | padding: 1%; 74 | } 75 | input { 76 | background-color: $lighter-color; 77 | border: none; 78 | padding: 2%; 79 | margin-bottom: 5%; 80 | border-radius: 5px; 81 | } 82 | } 83 | 84 | .fridge-wrapper { 85 | grid-area: fridge-wrapper; 86 | max-height: 40vh; 87 | padding: 5% 10% 5% 10%; 88 | overflow-y: scroll; 89 | border: 1px solid $button-color; 90 | border-radius: 5px; 91 | } 92 | 93 | .recipes-wrapper { 94 | grid-area: recipes-wrapper; 95 | max-height: 40vh; 96 | padding: 1% 20% 1% 20%; 97 | overflow-y: scroll; 98 | 99 | } 100 | 101 | .fridgeContainer { 102 | display: grid; 103 | grid-template-columns: 1fr 1fr 1fr 1fr; 104 | grid-template-rows: 1fr 1fr 1fr 1fr; 105 | gap: 0px 0px; 106 | grid-template-areas: 107 | "fridgeNav fridgeRec fridgeRec fridgeRec" 108 | "fridgeNav fridgeRec fridgeRec fridgeRec" 109 | "fridgeNav fridgeRec fridgeRec fridgeRec" 110 | "fridgeNav fridgeRec fridgeRec fridgeRec"; 111 | border: 1px solid $button-color; 112 | border-radius: 5px; 113 | box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px; 114 | } 115 | 116 | .fridgeNav { 117 | grid-area: fridgeNav; 118 | display: flex; 119 | align-items: center; 120 | justify-content: center; 121 | padding: 5%; 122 | border-right: 1px solid $button-color; 123 | button { 124 | margin-top: 5%; 125 | width: 150px; 126 | font-size: 1.1rem; 127 | padding: 5%; 128 | } 129 | box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px; 130 | } 131 | .fridgeRec { 132 | grid-area: fridgeRec; 133 | } 134 | 135 | .navBar { 136 | display: flex; 137 | flex-direction: row; 138 | justify-content: space-between; 139 | align-items: center; 140 | padding: .5% 1% .5% 2%; 141 | border: 1px solid $button-color; 142 | border-radius: 5px; 143 | h1 { 144 | font-size: 2rem; 145 | color: $header-color; 146 | text-shadow: 4px 4px 0px $button-color; 147 | } 148 | box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px; 149 | } 150 | 151 | .fridgeNav-wrapper { 152 | display: flex; 153 | flex-direction: column; 154 | align-items: center; 155 | justify-content: center; 156 | } 157 | 158 | .savedRecipes { 159 | padding: 3%; 160 | h3 { 161 | text-align: center; 162 | font-size: 1.5rem; 163 | } 164 | button { 165 | width: 150px; 166 | padding: .5%; 167 | } 168 | } 169 | 170 | .foodWrapper { 171 | box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px; 172 | label { 173 | font-size: 1.2rem; 174 | padding: 2%; 175 | background-color: $lighter-color; 176 | } 177 | padding: 2%; 178 | margin: 1%; 179 | width: 50%; 180 | background-color: $lighter-color; 181 | button { 182 | margin: 5%; 183 | } 184 | } 185 | 186 | .individualRecipe { 187 | box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px; 188 | p { 189 | font-size: 1.2rem; 190 | background-color: $lighter-color; 191 | } 192 | padding: 1% 3% 1% 3%; 193 | margin: 1%; 194 | button { 195 | width: 150px; 196 | padding: .5%; 197 | } 198 | background-color: $lighter-color; 199 | } 200 | 201 | .individualSaved { 202 | box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px; 203 | p { 204 | font-size: 1.2rem; 205 | background-color: $lighter-color; 206 | } 207 | padding: 1%; 208 | margin: 1%; 209 | background-color: $lighter-color; 210 | } 211 | 212 | .navButtonWrapper { 213 | display: flex; 214 | flex-direction: row-reverse; 215 | width: 50%; 216 | button { 217 | width: 130px; 218 | padding: 5%; 219 | } 220 | } --------------------------------------------------------------------------------