├── 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 |
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 |
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 |
80 |
81 |
82 |
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 | }
--------------------------------------------------------------------------------