├── .gitignore
├── README.md
├── backend
├── config
│ └── db.js
├── controllers
│ ├── recipesController.js
│ └── userController.js
├── middleware
│ ├── authMiddleware.js
│ └── errorMiddleware.js
├── models
│ ├── commentModel.js
│ ├── recipeModel.js
│ └── userModel.js
├── package-lock.json
├── package.json
├── routes
│ ├── recipeRoutes.js
│ └── userRoutes.js
└── server.js
├── frontend
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── index.html
│ └── uploads
│ │ ├── 66120547_716653968757263_2654443252404453376_n.jpg
│ │ ├── IMG_20220224_124020.jpg
│ │ ├── IMG_20220224_124200.jpg
│ │ ├── Screenshot 2022-08-19 at 13.38.46.png
│ │ ├── Screenshot 2022-08-19 at 13.49.23.png
│ │ ├── Screenshot 2022-08-19 at 9.39.53.png
│ │ ├── _tmp_IFJT2eQqQd3Ce1F4FZi9t8twc.jpg
│ │ ├── aaaaaaffsdfdsfdljk.jpg
│ │ ├── gazpacho.jpg
│ │ ├── jljdfdfdljk.jpg
│ │ ├── jljdfdfsfdsfsdfdsfdljk.jpg
│ │ ├── jljljk.jpg
│ │ ├── kartfdofi.jpeg
│ │ ├── kartofi.jpeg
│ │ ├── kartofkisyssirene.jpg
│ │ ├── lukasz-niescioruk-wvIM0l6NP0o-unsplash.jpg
│ │ ├── maleprofile.jpg
│ │ ├── mujlkjklsak.jpg
│ │ ├── muska.jpg
│ │ ├── novtarator.png
│ │ ├── register.png
│ │ ├── shopska-salata.jpeg
│ │ ├── spagetiboloneze.jpg
│ │ ├── tarafdfdtor.jpg
│ │ ├── tartdfsfdfsfsddsor3.jpeg
│ │ ├── tartdfsfdsor3.jpeg
│ │ ├── tartoch4e.jpeg
│ │ ├── tartofdfdch4e.jpeg
│ │ └── tikvichiki-po-gr.jpeg
└── src
│ ├── App.js
│ ├── App.module.css
│ ├── app
│ └── store.js
│ ├── assets
│ ├── black-marble-texture.jpg
│ ├── chicken-breast.jpg
│ ├── chicken.jpg
│ ├── chushka.jpg
│ ├── chushka2.jpg
│ ├── dish-kash.jpg
│ ├── eggs.jpg
│ ├── femaleprofile.jpg
│ ├── fish.jpg
│ ├── flat-lay-vegetables.jpg
│ ├── fresh-raw-meat.jpg
│ ├── fresh-salad.jpg
│ ├── healthy-diet.jpg
│ ├── healthy-dietwebp.webp
│ ├── healthy-ingredients.jpg
│ ├── maleprofile.jpg
│ ├── menu-top.jpg
│ ├── menu-top2.jpg
│ ├── mushrooms-basil.jpg
│ ├── mushrooms-basil2.jpg
│ ├── pastaCarbonara.jpg
│ ├── pink-marble-texture.jpg
│ ├── pizzaHomemade.jpg
│ ├── spice-pasta.jpg
│ ├── spinner.gif
│ ├── vegetables-table.jpg
│ ├── veggie-quinoa.jpg
│ ├── white-marble-texture.jpg
│ └── white-painted-wall.jpg
│ ├── components
│ ├── Home.js
│ ├── Home.module.css
│ ├── Login.js
│ ├── Login.module.css
│ ├── PrivateRoute.js
│ ├── Register.js
│ ├── Register.module.css
│ ├── layout
│ │ ├── Button.js
│ │ ├── Button.module.css
│ │ ├── Footer.js
│ │ ├── Footer.module.css
│ │ ├── Line.js
│ │ ├── Line.module.css
│ │ ├── MainNavigation.js
│ │ ├── MainNavigation.module.css
│ │ ├── Spinner.js
│ │ ├── Spinner.module.css
│ │ └── UploadFile.js
│ ├── profile
│ │ ├── EditProfile.js
│ │ ├── MyProfile.js
│ │ └── MyProfile.module.css
│ └── recipes
│ │ ├── Comments.js
│ │ ├── Comments.module.css
│ │ ├── EditRecipe.js
│ │ ├── EditRecipe.module.css
│ │ ├── PostRecipe.js
│ │ ├── PostRecipe.module.css
│ │ ├── Recipes.js
│ │ ├── Recipes.module.css
│ │ ├── SingleIngredient.js
│ │ ├── SingleIngredient.module.css
│ │ ├── SingleRecipe.js
│ │ ├── SingleRecipe.module.css
│ │ ├── SmallRecipeItem.js
│ │ └── SmallRecipeItem.module.css
│ ├── features
│ ├── auth
│ │ ├── authService.js
│ │ └── authSlice.js
│ └── recipes
│ │ ├── commentService.js
│ │ ├── commentSlice.js
│ │ ├── recipeService.js
│ │ └── recipeSlice.js
│ ├── index.css
│ └── index.js
├── package-lock.json
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-cooking-app
2 |
3 | DESCRIPTION
4 |
5 | Cooking blog where users can register and post a recipe, edit and delete their own recipes.
6 |
7 | USERS and functionalities
8 |
9 | Guest users
10 |
11 | -- Guest users can register and login to the application and access the public part that contains the recipes
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | LOGGED IN users
20 |
21 | They can view, update or delete their own recipe
22 | They can create new recipes
23 |
24 |
25 |
26 |
27 |
28 | Used technologies and packages
29 |
30 | FRONTEND
31 |
32 | React v 18.2.0
33 | Font Awesome
34 | CSS3
35 | react-router-dom v 6.3.0
36 | react-toastify v 9.0.7
37 | react-redux v 8.0.2
38 |
39 | BACKEND
40 | bcryptjs v 2.4.3
41 | colors v 1.4.0
42 | dotenv v 16.0.1
43 | express 4.18.1
44 | express-async-handler v 1.2.0
45 | jsonwebtoken v 8.5.1
46 | mongoose v 6.5.0
47 | multer v 1.4.5-lts.1
48 |
49 | Get started
50 |
51 | npm i
52 | cd frontend npm i
53 |
54 | Navigate to http://localhost:3000/
55 |
56 |
57 |
--------------------------------------------------------------------------------
/backend/config/db.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const connectDB = async () => {
4 | try {
5 | const conn = await mongoose.connect(process.env.MONGO_URI);
6 | console.log(`MongoDB Connected ${conn.connection.host}`.cyan.underline);
7 | } catch (error) {
8 | console.log(`Error: ${error.message}`.red.underline.bold);
9 | process.exit(1);
10 | }
11 | };
12 |
13 | module.exports = connectDB;
14 |
--------------------------------------------------------------------------------
/backend/controllers/recipesController.js:
--------------------------------------------------------------------------------
1 | const asyncHandler = require("express-async-handler");
2 | const User = require("../models/userModel");
3 | const Recipe = require("../models/recipeModel");
4 | const Comment = require("../models/commentModel");
5 |
6 | // @desc Get all recipes
7 | // @route GET api/posts
8 | // @access public
9 | const getRecipes = asyncHandler(async (req, res) => {
10 | const recipes = await Recipe.find({});
11 |
12 | if (recipes) {
13 | res.status(200).json(recipes);
14 | }
15 | });
16 |
17 | // @desc Get latest 3 recipes
18 | // @route GET api/posts
19 | // @access public
20 | const getLastThree = asyncHandler(async (req, res) => {
21 | const recipes = await Recipe.find({}).sort({ createdAt: -1 }).limit(3);
22 |
23 | if (recipes) {
24 | res.status(200).json(recipes);
25 | }
26 | });
27 |
28 | // @desc Get single recipe
29 | // @route api/posts/:id
30 | // @access public
31 | const getRecipeById = asyncHandler(async (req, res) => {
32 | const recipe = await Recipe.findById(req.params.id);
33 |
34 | if (!recipe) {
35 | res.status(404);
36 | throw new Error("Рецептата не е намерена");
37 | }
38 |
39 | res.status(200).json(recipe);
40 | });
41 |
42 | // @desc Post a recipe
43 | // @route POST /api/posts
44 | // @access for registered and logged in Users only
45 | const postRecipe = asyncHandler(async (req, res) => {
46 | const { title, preparation, suitableFor } = req.body;
47 | const photos = req.file.filename;
48 | const products = JSON.parse(req.body.products);
49 | // Validation
50 | // if (!title || !products || !preparation || !suitableFor) {
51 | // res.status(400);
52 | // throw new Error("Моля попълнете всички полета");
53 | // }
54 |
55 | // Get user using the id in the JWT
56 | const user = await User.findById(req.user.id);
57 |
58 | if (!user) {
59 | res.status(401);
60 | throw new Error("User not found");
61 | }
62 |
63 | // Create recipe
64 | const recipe = await Recipe.create({
65 | user: req.user.id,
66 | title,
67 | products,
68 | preparation,
69 | suitableFor,
70 | photos,
71 | });
72 |
73 | if (recipe) {
74 | res.status(201).json(recipe);
75 | } else {
76 | res.status(400);
77 | throw new Error("Невалидно въведена информация");
78 | }
79 | });
80 |
81 | // @desc Edit a recipe
82 | // @route PUT api/posts/:id
83 | // @access private
84 | const editRecipe = asyncHandler(async (req, res) => {
85 | const { title, preparation, suitableFor } = req.body;
86 | const photos = req.file.filename;
87 | const products = JSON.parse(req.body.products);
88 | const user = await User.findById(req.user.id);
89 | const recipeId = req.params;
90 |
91 | if (!user) {
92 | res.status(401);
93 | throw new Error("User not found");
94 | }
95 |
96 | const recipe = await Recipe.findById(req.params.id);
97 |
98 | if (!recipe) {
99 | res.status(404);
100 | throw new Error("Рецептата не е намерена");
101 | }
102 |
103 | if (recipe.user.toString() !== user.id) {
104 | res.status(401);
105 | throw new Error("Not authorized");
106 | }
107 |
108 | const recipeUpdate = {
109 | title,
110 | products,
111 | preparation,
112 | suitableFor,
113 | photos,
114 | };
115 |
116 | const updatedRecipe = await Recipe.findOneAndUpdate(recipeId, recipeUpdate, {
117 | new: true,
118 | });
119 |
120 | res.status(200).json(updatedRecipe);
121 | });
122 |
123 | // @desc get my list of recipes
124 | // @route api/posts/myRecipes
125 | // @access private
126 | const getMyRecipes = asyncHandler(async (req, res) => {
127 | // Get user using the id in the JWT
128 | const user = await User.findById(req.user.id);
129 |
130 | if (!user) {
131 | res.status(401);
132 | throw new Error("User not found");
133 | }
134 |
135 | const recipes = await Recipe.find({ user: req.user.id });
136 |
137 | if (recipes.user.toString() !== req.user.id) {
138 | res.status(401);
139 | throw new Error("Not authorized");
140 | }
141 |
142 | res.status(200).json(recipes);
143 | });
144 |
145 | // @desc Delete recipe
146 | // @route DELETE api/posts/:id
147 | // @access private
148 | const deleteRecipeById = asyncHandler(async (req, res) => {
149 | const user = await User.findById(req.user.id);
150 | if (!user) {
151 | res.status(401);
152 | throw new Error("User not found");
153 | }
154 |
155 | const recipe = await Recipe.findById(req.params.id);
156 |
157 | if (!recipe) {
158 | res.status(404);
159 | throw new Error("Рецептата не е намерена");
160 | }
161 |
162 | if (recipe.user.toString() !== req.user.id) {
163 | res.status(401);
164 | throw new Error("Not authorized");
165 | }
166 |
167 | await recipe.remove();
168 |
169 | res.status(200).json({ sucess: true });
170 | });
171 |
172 | // @desc Post a comment
173 | // @route POST /api/comments
174 | // @access for registered and logged in Users only
175 | const postComment = asyncHandler(async (req, res) => {
176 | const { recipeId, name, comment } = req.body;
177 | // Validation
178 | // if (!title || !products || !preparation || !suitableFor) {
179 | // res.status(400);
180 | // throw new Error("Моля попълнете всички полета");
181 | // }
182 |
183 | // Get user using the id in the JWT
184 | const user = await User.findById(req.user.id);
185 |
186 | if (!user) {
187 | res.status(401);
188 | throw new Error("User not found");
189 | }
190 |
191 | // POST comment
192 | const newComment = await Comment.create({
193 | recipe: recipeId,
194 | name,
195 | comment,
196 | });
197 |
198 | if (newComment) {
199 | res.status(201).json(newComment);
200 | } else {
201 | res.status(400);
202 | throw new Error("Невалидно въведена информация");
203 | }
204 | });
205 |
206 | // GET all comments
207 | // @desc Get single recipe
208 | // @route api/posts/:id
209 | // @access public
210 | const getAllComments = asyncHandler(async (req, res) => {
211 | const comments = await Comment.find({ recipe: req.params.id });
212 |
213 | if (!comments) {
214 | res.status(404);
215 | throw new Error("Няма коментари към тази рецепта");
216 | }
217 |
218 | res.status(200).json(comments);
219 | });
220 |
221 | module.exports = {
222 | postRecipe,
223 | getMyRecipes,
224 | editRecipe,
225 | getRecipeById,
226 | getRecipes,
227 | deleteRecipeById,
228 | getLastThree,
229 | postComment,
230 | getAllComments,
231 | };
232 |
--------------------------------------------------------------------------------
/backend/controllers/userController.js:
--------------------------------------------------------------------------------
1 | const asyncHandler = require("express-async-handler");
2 | const bcrypt = require("bcryptjs");
3 | const jwt = require("jsonwebtoken");
4 |
5 | const User = require("../models/userModel");
6 |
7 | // @desc Register a new user
8 | // @route /api/users
9 | // @access Public
10 | const registerUser = asyncHandler(async (req, res) => {
11 | const { name, email, password, gender } = req.body;
12 |
13 | // Validation
14 | if (!name || !email || !password || !gender) {
15 | res.status(400);
16 | throw new Error("Моля въведете всички полета");
17 | }
18 |
19 | // Find if user already exists
20 | const userExists = await User.findOne({ email });
21 |
22 | if (userExists) {
23 | res.status(400);
24 | throw new Error("Акаунт с такъв имейл адрес вече съществува");
25 | }
26 |
27 | // Hash password
28 | const salt = await bcrypt.genSalt(10);
29 | const hashedPassword = await bcrypt.hash(password, salt);
30 |
31 | // Create user
32 | const user = await User.create({
33 | name,
34 | email,
35 | password: hashedPassword,
36 | gender,
37 | });
38 |
39 | if (user) {
40 | res.status(201).json({
41 | id: user._id,
42 | name: user.name,
43 | email: user.email,
44 | token: generateToken(user._id),
45 | gender: user.gender
46 | });
47 | } else {
48 | res.status(400);
49 | throw new Error("Невалидни данни");
50 | }
51 | });
52 |
53 | // @desc Login a user
54 | // @route /api/users/login
55 | // @access Public
56 | const loginUser = asyncHandler(async (req, res) => {
57 | const { email, password } = req.body;
58 |
59 | const user = await User.findOne({ email });
60 |
61 | // Check user and passwords matchs
62 | if (user && (await bcrypt.compare(password, user.password))) {
63 | res.status(200).json({
64 | id: user._id,
65 | name: user.name,
66 | email: user.email,
67 | token: generateToken(user._id),
68 | });
69 | } else {
70 | res.status(401);
71 | throw new Error("Невалидни имейл или парола");
72 | }
73 | });
74 |
75 | // @desc get current user
76 | // @route api/users/me
77 | // @access private
78 | const getMe = asyncHandler(async (req, res) => {
79 | const user = {
80 | id: req.user._id,
81 | email: req.user.email,
82 | name: req.user.name,
83 | };
84 | res.status(200).json(user);
85 | });
86 |
87 | // Generate token
88 | const generateToken = (id) => {
89 | return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: "30d" });
90 | };
91 |
92 | module.exports = {
93 | registerUser,
94 | loginUser,
95 | getMe,
96 | };
97 |
98 | // function registerUser(req, res) {
99 | // //
100 | // }
101 |
--------------------------------------------------------------------------------
/backend/middleware/authMiddleware.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const jwt = require("jsonwebtoken");
3 | const asyncHandler = require("express-async-handler");
4 | const User = require("../models/userModel");
5 |
6 | const protect = asyncHandler(async (req, res, next) => {
7 | let token;
8 |
9 | if (
10 | req.headers.authorization &&
11 | req.headers.authorization.startsWith("Bearer")
12 | ) {
13 | try {
14 | // Get token from header
15 | token = req.headers.authorization.split(" ")[1];
16 | // Verify token
17 | const decoded = jwt.verify(token, process.env.JWT_SECRET);
18 | // Get user from token
19 | req.user = await User.findById(decoded.id).select("-password");
20 |
21 | next();
22 | } catch (error) {
23 | console.log(error);
24 | res.status(401);
25 | throw new Error("Not authorized");
26 | }
27 | }
28 |
29 | if (!token) {
30 | res.status(401);
31 | throw new Error("Not authorized");
32 | }
33 | });
34 |
35 | module.exports = { protect };
36 |
--------------------------------------------------------------------------------
/backend/middleware/errorMiddleware.js:
--------------------------------------------------------------------------------
1 | const errorHandler = (err, req, res, next) => {
2 | const statusCode = res.statusCode ? res.statusCode : 500;
3 |
4 | res.status(statusCode);
5 | res.json({
6 | message: err.message,
7 | stack: process.env.NODE_ENV === "production" ? null : err.stack,
8 | });
9 | };
10 |
11 | module.exports = { errorHandler };
12 |
--------------------------------------------------------------------------------
/backend/models/commentModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const commentSchema = mongoose.Schema(
4 | {
5 | recipe: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | required: true,
8 | ref: "Recipe",
9 | },
10 | name: { type: String, required: true },
11 | comment: { type: String, required: [true, "Моля добавете коментар"] },
12 | },
13 | { timestamps: true }
14 | );
15 |
16 | module.exports = mongoose.model("Comment", commentSchema);
17 |
--------------------------------------------------------------------------------
/backend/models/recipeModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const recipeSchema = mongoose.Schema(
4 | {
5 | user: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | required: true,
8 | ref: "User",
9 | },
10 | title: {
11 | type: String,
12 | required: [true, "Моля добавете заглавие на рецептата"],
13 | },
14 | products: {
15 | type: Array,
16 | required: [true, "Моля добавете продукти"],
17 | },
18 | photos: { type: String, required: [true, "Моля добавете снимки"] },
19 | preparation: { type: String, required: [true, "Моля добавете стъпки"] },
20 | suitableFor: { type: Number, required: true, enum: [1, 2, 4, 6] },
21 | },
22 | { timestamps: true }
23 | );
24 |
25 | module.exports = mongoose.model("Recipe", recipeSchema);
26 |
--------------------------------------------------------------------------------
/backend/models/userModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const userSchema = mongoose.Schema(
4 | {
5 | name: { type: String, required: [true, "Моля добавете име"] },
6 | email: {
7 | type: String,
8 | required: [true, "Моля добавете имейл"],
9 | unique: true,
10 | },
11 | profilePic: { type: String, required: false },
12 | password: { type: String, required: [true, "Моля добавете парола"] },
13 | postedRecipes: {type: Number, required: false},
14 | gender: {type: String, required: true, enum: ['male', 'female']}
15 | },
16 | { timestamps: true }
17 | );
18 |
19 | module.exports = mongoose.model("User", userSchema);
20 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-helpdesk-app",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "npm install",
8 | "start": "node server.js",
9 | "server": "nodemon server.js"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "bcryptjs": "^2.4.3",
16 | "colors": "^1.4.0",
17 | "dotenv": "^16.0.1",
18 | "express": "^4.18.1",
19 | "express-async-handler": "^1.2.0",
20 | "jsonwebtoken": "^8.5.1",
21 | "mongoose": "^6.5.0",
22 | "multer": "^1.4.5-lts.1"
23 | },
24 | "devDependencies": {
25 | "nodemon": "^2.0.19"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/backend/routes/recipeRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 | const {
4 | postRecipe,
5 | getMyRecipes,
6 | editRecipe,
7 | getRecipeById,
8 | getRecipes,
9 | deleteRecipeById,
10 | getLastThree,
11 | postComment,
12 | getAllComments,
13 | } = require("../controllers/recipesController");
14 |
15 | const { protect } = require("../middleware/authMiddleware");
16 | const multer = require("multer");
17 | const path = require("path");
18 |
19 | const storage = multer.diskStorage({
20 | destination: (req, file, cb) => {
21 | cb(null, "./frontend/public/uploads");
22 | },
23 | filename: (req, file, cb) => {
24 | console.log(file);
25 |
26 | cb(null, file.originalname);
27 | // UNUQIE FILENAME
28 | //cb(null, Date.now() + path.extname(file.originalname));
29 | },
30 | });
31 | const upload = multer({ storage: storage });
32 |
33 | // POST, EDIT and DELETE recipes
34 | router.post("/", protect, upload.single("photos"), postRecipe);
35 | router.put("/:id", protect, upload.single("photos"), editRecipe);
36 | router.delete("/:id", protect, deleteRecipeById);
37 |
38 | // GET list of user's recipes
39 | router.get("/myRecipes", getMyRecipes);
40 |
41 | // GET public recipes and single recipe
42 | router.get("/", getRecipes);
43 | router.get("/lastThree", getLastThree);
44 | router.get("/:id", getRecipeById);
45 |
46 | // GET, POST and DELETE comments
47 | router.post("/comments", protect, postComment);
48 | router.get("/:id/comments", getAllComments);
49 |
50 | module.exports = router;
51 |
--------------------------------------------------------------------------------
/backend/routes/userRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 | const {
4 | registerUser,
5 | loginUser,
6 | getMe,
7 | } = require("../controllers/userController");
8 |
9 | const { protect } = require("../middleware/authMiddleware");
10 |
11 | router.post("/", registerUser);
12 | router.post("/login", loginUser);
13 | router.get("/me", protect, getMe);
14 |
15 | module.exports = router;
16 |
--------------------------------------------------------------------------------
/backend/server.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const path = require("path");
3 | const dotenv = require("dotenv").config();
4 | const colors = require("colors");
5 | const connectDB = require("./config/db");
6 | const { errorHandler } = require("./middleware/errorMiddleware");
7 | const PORT = process.env.PORT || 8000;
8 |
9 | // Connect to Database
10 | connectDB();
11 |
12 | const app = express();
13 |
14 | app.use(express.json());
15 |
16 | app.use(express.urlencoded({ extended: false }));
17 |
18 | app.use((req, res, next) => {
19 | res.setHeader("Access-Control-Allow-Credentials", "true");
20 | res.header("Access-Control-Allow-Origin", "*");
21 | res.header(
22 | "Access-Control-Allow-Headers",
23 | "Access-Control-Allow-Headers, Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Request-Method, Access-Control-Request-Headers"
24 | );
25 | res.header(
26 | "Access-Control-Allow-Methods",
27 | "GET, POST, PUT, PATCH, DELETE, OPTIONS"
28 | );
29 | next();
30 | });
31 |
32 | // Routes
33 | app.use("/api/users", require("./routes/userRoutes"));
34 | app.use("/api/posts", require("./routes/recipeRoutes"));
35 |
36 | // Server Frontend
37 | // if (process.env.NODE_ENV === "production") {
38 | // // Set build folder as static
39 | // app.use(express.static(path.join(__dirname, "../frontend/build")));
40 |
41 | // app.get("*", (req, res) =>
42 | // res.sendFile(__dirname, "../", "frontend", "build", "index.html")
43 | // );
44 | // } else {
45 | // // ADDED API here!!!
46 | // app.get("/api", (req, res) => {
47 | // res.send("Hello");
48 | // });
49 | // }
50 |
51 | app.use(errorHandler);
52 |
53 | app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
54 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@reduxjs/toolkit": "^1.8.3",
7 | "@testing-library/jest-dom": "^5.16.5",
8 | "@testing-library/react": "^13.3.0",
9 | "@testing-library/user-event": "^13.5.0",
10 | "react": "^18.2.0",
11 | "react-dom": "^18.2.0",
12 | "react-icons": "^4.4.0",
13 | "react-redux": "^8.0.2",
14 | "react-router-dom": "^6.3.0",
15 | "react-scripts": "5.0.1",
16 | "react-toastify": "^9.0.7",
17 | "redux": "^4.2.0",
18 | "uuidv4": "^6.2.13",
19 | "web-vitals": "^2.1.4"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app",
30 | "react-app/jest"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | },
45 | "description": "This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).",
46 | "main": "index.js",
47 | "keywords": [],
48 | "author": "",
49 | "license": "ISC"
50 | }
51 |
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 | Cooking Blog
15 |
16 |
17 | You need to enable JavaScript to run this app.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/frontend/public/uploads/66120547_716653968757263_2654443252404453376_n.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/66120547_716653968757263_2654443252404453376_n.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/IMG_20220224_124020.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/IMG_20220224_124020.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/IMG_20220224_124200.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/IMG_20220224_124200.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/Screenshot 2022-08-19 at 13.38.46.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/Screenshot 2022-08-19 at 13.38.46.png
--------------------------------------------------------------------------------
/frontend/public/uploads/Screenshot 2022-08-19 at 13.49.23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/Screenshot 2022-08-19 at 13.49.23.png
--------------------------------------------------------------------------------
/frontend/public/uploads/Screenshot 2022-08-19 at 9.39.53.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/Screenshot 2022-08-19 at 9.39.53.png
--------------------------------------------------------------------------------
/frontend/public/uploads/_tmp_IFJT2eQqQd3Ce1F4FZi9t8twc.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/_tmp_IFJT2eQqQd3Ce1F4FZi9t8twc.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/aaaaaaffsdfdsfdljk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/aaaaaaffsdfdsfdljk.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/gazpacho.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/gazpacho.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/jljdfdfdljk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/jljdfdfdljk.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/jljdfdfsfdsfsdfdsfdljk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/jljdfdfsfdsfsdfdsfdljk.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/jljljk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/jljljk.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/kartfdofi.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/kartfdofi.jpeg
--------------------------------------------------------------------------------
/frontend/public/uploads/kartofi.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/kartofi.jpeg
--------------------------------------------------------------------------------
/frontend/public/uploads/kartofkisyssirene.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/kartofkisyssirene.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/lukasz-niescioruk-wvIM0l6NP0o-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/lukasz-niescioruk-wvIM0l6NP0o-unsplash.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/maleprofile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/maleprofile.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/mujlkjklsak.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/mujlkjklsak.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/muska.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/muska.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/novtarator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/novtarator.png
--------------------------------------------------------------------------------
/frontend/public/uploads/register.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/register.png
--------------------------------------------------------------------------------
/frontend/public/uploads/shopska-salata.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/shopska-salata.jpeg
--------------------------------------------------------------------------------
/frontend/public/uploads/spagetiboloneze.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/spagetiboloneze.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/tarafdfdtor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/tarafdfdtor.jpg
--------------------------------------------------------------------------------
/frontend/public/uploads/tartdfsfdfsfsddsor3.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/tartdfsfdfsfsddsor3.jpeg
--------------------------------------------------------------------------------
/frontend/public/uploads/tartdfsfdsor3.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/tartdfsfdsor3.jpeg
--------------------------------------------------------------------------------
/frontend/public/uploads/tartoch4e.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/tartoch4e.jpeg
--------------------------------------------------------------------------------
/frontend/public/uploads/tartofdfdch4e.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/tartofdfdch4e.jpeg
--------------------------------------------------------------------------------
/frontend/public/uploads/tikvichiki-po-gr.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/public/uploads/tikvichiki-po-gr.jpeg
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | //import classes from "./App.module.css";
2 | import { ToastContainer } from "react-toastify";
3 | import "react-toastify/dist/ReactToastify.css";
4 |
5 | import { Routes, Route } from "react-router-dom";
6 |
7 | import Footer from "./components/layout/Footer";
8 | import MainNavigation from "./components/layout/MainNavigation";
9 | import PrivateRoute from "./components/PrivateRoute";
10 |
11 | import Register from "./components/Register";
12 | import Home from "./components/Home";
13 | import Login from "./components/Login";
14 | import MyProfile from "./components/profile/MyProfile";
15 | import PostRecipe from "./components/recipes/PostRecipe";
16 | import SingleRecipe from "./components/recipes/SingleRecipe";
17 | import EditProfile from "./components/profile/EditProfile";
18 | import EditRecipe from "./components/recipes/EditRecipe";
19 |
20 | import React from "react";
21 |
22 | function App() {
23 | return (
24 |
25 |
26 |
27 | }>
28 | }>
29 | }>
30 | }>
31 | } />
32 |
33 | }>
34 | } />
35 |
36 | }>
37 | } />
38 | {/* }> */}
39 | }>
40 | }>
41 | } />
42 |
43 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 |
51 | export default App;
52 |
--------------------------------------------------------------------------------
/frontend/src/App.module.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::before,
3 | *::after {
4 | box-sizing: border-box;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | .main__recipe__image {
10 | max-width: 720px;
11 | max-height: 720px;
12 | }
13 |
14 | .recipeMain {
15 | max-width: 720px;
16 | margin: auto;
17 | margin-top: 60px;
18 | }
19 |
20 | .recipe__list {
21 | margin-top: 2rem;
22 | margin-bottom: 8rem;
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/app/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import authReducer from "../features/auth/authSlice";
3 | import recipeReducer from "../features/recipes/recipeSlice";
4 | import commentReducer from "../features/recipes/commentSlice";
5 |
6 | export const store = configureStore({
7 | reducer: {
8 | auth: authReducer,
9 | recipe: recipeReducer,
10 | comment: commentReducer,
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/frontend/src/assets/black-marble-texture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/black-marble-texture.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/chicken-breast.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/chicken-breast.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/chicken.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/chicken.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/chushka.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/chushka.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/chushka2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/chushka2.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/dish-kash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/dish-kash.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/eggs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/eggs.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/femaleprofile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/femaleprofile.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/fish.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/fish.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/flat-lay-vegetables.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/flat-lay-vegetables.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/fresh-raw-meat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/fresh-raw-meat.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/fresh-salad.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/fresh-salad.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/healthy-diet.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/healthy-diet.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/healthy-dietwebp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/healthy-dietwebp.webp
--------------------------------------------------------------------------------
/frontend/src/assets/healthy-ingredients.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/healthy-ingredients.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/maleprofile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/maleprofile.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/menu-top.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/menu-top.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/menu-top2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/menu-top2.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/mushrooms-basil.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/mushrooms-basil.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/mushrooms-basil2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/mushrooms-basil2.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/pastaCarbonara.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/pastaCarbonara.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/pink-marble-texture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/pink-marble-texture.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/pizzaHomemade.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/pizzaHomemade.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/spice-pasta.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/spice-pasta.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/spinner.gif
--------------------------------------------------------------------------------
/frontend/src/assets/vegetables-table.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/vegetables-table.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/veggie-quinoa.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/veggie-quinoa.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/white-marble-texture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/white-marble-texture.jpg
--------------------------------------------------------------------------------
/frontend/src/assets/white-painted-wall.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlinehub0808/reactCookingApp/ab1799695ecd939d93b53199c661197ad16180f4/frontend/src/assets/white-painted-wall.jpg
--------------------------------------------------------------------------------
/frontend/src/components/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | import classes from "./Home.module.css";
4 | import SmallRecipeItem from "./recipes/SmallRecipeItem";
5 | import Spinner from "./layout/Spinner";
6 | import { getLastThree, reset } from "../features/recipes/recipeSlice";
7 | //import Button from "./layout/Button";
8 |
9 | const Home = () => {
10 | const { recipes, isLoading, isSuccess } = useSelector(
11 | (state) => state.recipe
12 | );
13 | const dispatch = useDispatch();
14 |
15 | useEffect(() => {
16 | dispatch(getLastThree());
17 | }, [dispatch]);
18 |
19 | useEffect(() => {
20 | return () => {
21 | if (isSuccess) {
22 | dispatch(reset());
23 | }
24 | };
25 | }, [isSuccess, dispatch]);
26 |
27 | if (isLoading) {
28 | return ;
29 | }
30 |
31 | return (
32 |
33 |
34 | {/*
Рецепта на деня
35 |
36 | Салатка
37 |
38 |
43 |
44 |
45 |
46 |
47 | Lorem Ipsum is simply dummy text of the printing and typesetting
48 | industry. Lorem Ipsum has been the industry's standard dummy text
49 | ever since the 1500s, when an unknown printer took a galley of
50 | type and scrambled it to make a type specimen book. It has
51 | survived not only five centuries, but also the leap into
52 | electronic typesetting, remaining essentially unchanged. It was
53 | popularised in the 1960s with the release of Letraset sheets
54 | containing Lorem Ipsum passages, and more recently with desktop
55 | publishing software like Aldus PageMaker including versions of
56 | Lorem Ipsum.
57 |
58 |
59 |
60 | */}
61 |
62 |
63 |
64 | {recipes
65 | ? recipes.map((recipe) => (
66 |
71 | ))
72 | : null}
73 |
74 |
75 |
76 |
77 | );
78 | };
79 |
80 | export default Home;
81 |
--------------------------------------------------------------------------------
/frontend/src/components/Home.module.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::before,
3 | *::after {
4 | box-sizing: border-box;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | .background {
10 | background-image: url("../assets/healthy-dietwebp.webp");
11 | min-height: 180vh;
12 | background-repeat: no-repeat;
13 | background-size: cover;
14 | }
15 |
16 | .main__recipe__image {
17 | max-width: 720px;
18 | max-height: 520px;
19 | margin-top: 40px;
20 | -webkit-box-shadow: 0px 0px 10px 3px rgba(0, 0, 0, 0.5);
21 | box-shadow: 0px 0px 10px 3px rgba(0, 0, 0, 0.5);
22 | border-radius: 20px;
23 | }
24 |
25 | .recipeMain {
26 | max-width: 720px;
27 | margin: auto;
28 | padding-top: 20px;
29 | }
30 |
31 | .recipe__list {
32 | margin-top: 2rem;
33 | margin-bottom: 8rem;
34 | }
35 |
36 | .recipeOfTheDay {
37 | font-family: "Montserrat", sans-serif;
38 | text-align: center;
39 | }
40 |
41 | .recipeTitle {
42 | font-family: "Pacifico", cursive;
43 | font-size: 44px;
44 | text-align: center;
45 | }
46 |
47 | .para {
48 | padding-top: 24px;
49 | font-family: "Montserrat", sans-serif;
50 | }
51 | /*
52 | font-family: 'Caveat', cursive;
53 |
54 | font-family: 'Lobster', cursive;
55 |
56 | font-family: 'Marck Script', cursive;
57 |
58 | font-family: 'Montserrat', sans-serif;
59 |
60 | font-family: 'Nunito', sans-serif;
61 |
62 | font-family: 'Pacifico', cursive;
63 | */
64 |
--------------------------------------------------------------------------------
/frontend/src/components/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import classes from "./Login.module.css";
4 | import Spinner from "./layout/Spinner";
5 | import { useSelector, useDispatch } from "react-redux";
6 | import { login, reset } from "../features/auth/authSlice";
7 | import { toast } from "react-toastify";
8 |
9 | const Login = (props) => {
10 | const [formData, setFormData] = useState({
11 | email: "",
12 | password: "",
13 | });
14 | const navigate = useNavigate();
15 |
16 | const dispatch = useDispatch();
17 | const { user, isLoading, isError, isSuccess, message } = useSelector(
18 | (state) => state.auth
19 | );
20 | const { email, password } = formData;
21 |
22 | useEffect(() => {
23 | if (isError) {
24 | toast.error(message);
25 | }
26 |
27 | if (isSuccess || user) {
28 | navigate("/");
29 | }
30 |
31 | dispatch(reset());
32 | }, [isError, isSuccess, user, message, navigate, dispatch]);
33 |
34 | const onChange = (e) => {
35 | setFormData((prevState) => ({
36 | ...prevState,
37 | [e.target.name]: e.target.value,
38 | }));
39 | };
40 |
41 | const onSubmit = (e) => {
42 | e.preventDefault();
43 |
44 | const userData = {
45 | email,
46 | password,
47 | };
48 |
49 | dispatch(login(userData));
50 | };
51 |
52 | if (isLoading) {
53 | return ;
54 | }
55 |
56 | return (
57 |
58 |
59 |
88 |
89 | Ако все още нямаш регистрация с нас{" "}
90 |
91 | регистрирай се тук
92 |
93 |
94 |
95 |
96 | );
97 | };
98 |
99 | export default Login;
100 |
--------------------------------------------------------------------------------
/frontend/src/components/Login.module.css:
--------------------------------------------------------------------------------
1 | .background {
2 | background-image: url("../assets/spice-pasta.jpg");
3 | height: 110vh;
4 | }
5 |
6 | .reg {
7 | padding-top: 6rem;
8 | color: white;
9 | padding-left: 20%;
10 | width: 100%;
11 | max-width: 800px;
12 | }
13 |
14 | .para {
15 | margin-top: 14px;
16 | }
17 |
18 | .link__to {
19 | color: var(--pinky);
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/components/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import { Outlet, Navigate } from "react-router-dom";
2 | import { useSelector } from "react-redux";
3 |
4 | const PrivateRoute = () => {
5 |
6 | const { user } = useSelector((state) => state.auth)
7 |
8 | return user ? :
9 | }
10 | export default PrivateRoute;
--------------------------------------------------------------------------------
/frontend/src/components/Register.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import { toast } from "react-toastify";
4 | import classes from "./Register.module.css";
5 | import { useSelector, useDispatch } from "react-redux";
6 | import { register, reset } from "../features/auth/authSlice";
7 | import Spinner from "./layout/Spinner";
8 |
9 | const Register = () => {
10 | const [formData, setFormData] = useState({
11 | name: "",
12 | email: "",
13 | password: "",
14 | repass: "",
15 | gender: "",
16 | });
17 |
18 | const navigate = useNavigate();
19 |
20 | const { name, email, password, repass, gender } = formData;
21 |
22 | const dispatch = useDispatch();
23 | const { user, isLoading, isError, isSuccess, message } = useSelector(
24 | (state) => state.auth
25 | );
26 |
27 | useEffect(() => {
28 | if (isError) {
29 | toast.error(message);
30 | }
31 |
32 | if (isSuccess || user) {
33 | navigate("/");
34 | }
35 |
36 | dispatch(reset());
37 | }, [isError, isSuccess, user, message, navigate, dispatch]);
38 |
39 | const onChange = (e) => {
40 | setFormData((prevState) => ({
41 | ...prevState,
42 | [e.target.name]: e.target.value,
43 | }));
44 | };
45 |
46 | const onGenderSelect = (e) => {
47 | setFormData((prevState) => ({
48 | ...prevState,
49 | [e.target.name]: e.target.id,
50 | }));
51 | };
52 |
53 | const onSubmit = async (e) => {
54 | e.preventDefault();
55 |
56 | if (password.length < 6 || password.length > 12) {
57 | toast.error("Паролата трябва да е между 6 и 12 символа");
58 | return;
59 | }
60 | if (password !== repass) {
61 | toast.error("Въведените пароли не съвпадат");
62 | return;
63 | }
64 |
65 | const userData = {
66 | name,
67 | email,
68 | password,
69 | gender,
70 | };
71 |
72 | dispatch(register(userData));
73 | };
74 |
75 | if (isLoading) {
76 | return ;
77 | }
78 |
79 | return (
80 |
81 |
82 |
163 |
164 | Ако вече имаш регистрация{" "}
165 |
166 | Влез тук
167 |
168 |
169 |
170 |
171 | );
172 | };
173 |
174 | export default Register;
175 |
--------------------------------------------------------------------------------
/frontend/src/components/Register.module.css:
--------------------------------------------------------------------------------
1 | .background {
2 | background-image: url("../assets/spice-pasta.jpg");
3 | height: 110vh;
4 | }
5 |
6 | .reg {
7 | padding-top: 6rem;
8 | color: white;
9 | padding-left: 20%;
10 | width: 100%;
11 | max-width: 800px;
12 | }
13 |
14 | .reg__title {
15 | font-size: 38px;
16 | margin-bottom: 1rem;
17 | }
18 |
19 | .genRadioLabel {
20 | font-size: 20px;
21 | font-family: "Nunito", sans-serif;
22 | margin-left: 2rem;
23 | margin-right: 2rem;
24 | text-align: center;
25 | }
26 |
27 | .genRadio {
28 | margin-top: 0.5rem;
29 | display: inline;
30 | font-size: 28px;
31 | margin-bottom: 1.5rem;
32 | }
33 |
34 | .para {
35 | margin-bottom: 20px;
36 | margin-top: -20px;
37 | }
38 |
39 | .para_second {
40 | margin-top: 14px;
41 | }
42 |
43 | .link__to {
44 | color: var(--pinky);
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/Button.js:
--------------------------------------------------------------------------------
1 | import classes from "./Button.module.css";
2 |
3 | const Button = (props) => {
4 | return {props.btnText} ;
5 | };
6 |
7 | export default Button;
8 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/Button.module.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | font-family: "Caveat", cursive;
3 | color: black;
4 | background-color: chartreuse;
5 | padding: 12px 20px;
6 | border-radius: 15px;
7 | font-size: 18px;
8 | text-transform: uppercase;
9 | margin-top: 20px;
10 | }
11 |
12 | .btn:hover {
13 | background-color: rgb(86, 168, 5);
14 | color: black;
15 | cursor: pointer;
16 | }
17 |
18 | .btnWarning {
19 | background-color: orange;
20 | }
21 |
22 | /*
23 | font-family: 'Caveat', cursive;
24 |
25 | font-family: 'Lobster', cursive;
26 |
27 | font-family: 'Marck Script', cursive;
28 |
29 | font-family: 'Montserrat', sans-serif;
30 |
31 | font-family: 'Nunito', sans-serif;
32 |
33 | font-family: 'Pacifico', cursive;
34 | */
35 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/Footer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import classes from "./Footer.module.css";
3 |
4 | const Footer = () => {
5 | return (
6 |
9 | );
10 | };
11 |
12 | export default Footer;
13 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/Footer.module.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | height: 4rem;
3 | background-color: #000036;
4 | display: flex;
5 | }
6 |
7 | .footer p {
8 | vertical-align: middle;
9 | color: white;
10 | padding-left: 40px;
11 | padding-top: 1.5rem;
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/Line.js:
--------------------------------------------------------------------------------
1 | import classes from "./Line.module.css";
2 |
3 | const Line = () => {
4 | return
;
5 | };
6 |
7 | export default Line;
8 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/Line.module.css:
--------------------------------------------------------------------------------
1 | .line {
2 | border-bottom: 3px double var(--purple);
3 | border-radius: 0px 0px 25px 25px;
4 | width: 60%;
5 | margin-top: 16px;
6 | margin-bottom: 16px;
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/MainNavigation.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import classes from "./MainNavigation.module.css";
4 | import { useSelector, useDispatch } from "react-redux";
5 | import { logout, reset } from "../../features/auth/authSlice";
6 |
7 | const MainNavigation = (props) => {
8 | const navigate = useNavigate();
9 | const dispatch = useDispatch();
10 | const { user } = useSelector((state) => state.auth);
11 |
12 | const onLogout = () => {
13 | dispatch(logout());
14 | dispatch(reset());
15 | navigate("/");
16 | };
17 |
18 | return (
19 |
61 | );
62 | };
63 |
64 | export default MainNavigation;
65 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/MainNavigation.module.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::before,
3 | *::after {
4 | box-sizing: border-box;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | .header {
10 | width: 100%;
11 | height: 100px;
12 | background-color: var(--light-purple);
13 | display: flex;
14 | justify-content: space-between;
15 | align-items: center;
16 | padding: 0 10%;
17 | }
18 |
19 | .logo {
20 | color: black;
21 | font-size: 26px;
22 | font-weight: 900;
23 | }
24 |
25 | .lis {
26 | display: flex;
27 | gap: 1rem;
28 | }
29 |
30 | .lis li {
31 | list-style: none;
32 | text-transform: uppercase;
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/Spinner.js:
--------------------------------------------------------------------------------
1 | import spinner from "../../assets/spinner.gif";
2 | import classes from "./Spinner.module.css";
3 |
4 | function Spinner() {
5 | return (
6 |
7 |
13 |
14 | );
15 | }
16 |
17 | export default Spinner;
18 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/Spinner.module.css:
--------------------------------------------------------------------------------
1 | .center {
2 | margin-top: 10rem;
3 | margin: auto;
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/src/components/layout/UploadFile.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { useNavigate } from "react-router-dom";
4 | import { uploadNewPhoto } from "../../features/recipes/recipeSlice";
5 |
6 | const UploadFile = (props) => {
7 | const [uploadedPhotos, setUploadPhotos] = useState([]);
8 | const dispatch = useDispatch();
9 | const navigate = useNavigate();
10 | const { recipe, isLoading, isError, isSuccess, message } = useSelector(
11 | (state) => state.recipe
12 | );
13 |
14 | const onFileUpload = (e) => {
15 | setUploadPhotos(e.target.files[0]);
16 |
17 | props.onUploadClick(e.target.files[0].name);
18 | };
19 |
20 | const onImageUpload = (e) => {
21 | e.preventDefault();
22 |
23 | const formData = new FormData();
24 | formData.append("image", uploadedPhotos);
25 | console.log(formData.values);
26 | dispatch(uploadNewPhoto(formData));
27 | if (isSuccess) {
28 | navigate("/");
29 | }
30 | if (isError) {
31 | console.log(message);
32 | }
33 | };
34 |
35 | return (
36 |
37 |
49 |
50 | );
51 | };
52 |
53 | export default UploadFile;
54 |
--------------------------------------------------------------------------------
/frontend/src/components/profile/EditProfile.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Link, useNavigate, useParams } from "react-router-dom";
3 | import { toast } from "react-toastify";
4 | import classes from "../Register.module.css";
5 | import {useSelector, useDispatch} from 'react-redux';
6 | import { register, reset } from "../../features/auth/authSlice";
7 | import Spinner from "../layout/Spinner";
8 |
9 | const EditProfile = (props) => {
10 | const { user } = useSelector((state) => state.auth);
11 | const params = useParams()
12 | const {profileId} = params
13 | const [formData, setFormData] = useState({
14 | name: "",
15 | email: "",
16 | password: "",
17 | repass: "",
18 | });
19 |
20 | // const navigate = useNavigate();
21 |
22 | const { name, email, password, repass } = formData;
23 |
24 | const dispatch = useDispatch();
25 | const { isLoading, isError ,isSuccess, message} = useSelector(state => state.auth)
26 |
27 | // useEffect(() => {
28 | // if (isError) {
29 | // toast.error(message)
30 | // }
31 |
32 | // // if (isSuccess || user) {
33 | // // navigate('/')
34 | // // }
35 |
36 | // dispatch(reset())
37 | // }, [isError, isSuccess, user, message, navigate, dispatch])
38 |
39 | const onChange = (e) => {
40 | setFormData((prevState) => ({
41 | ...prevState,
42 | [e.target.name]: e.target.value,
43 | }));
44 | };
45 |
46 | const onSubmit = async (e) => {
47 | e.preventDefault();
48 |
49 | if (password.length < 6 || password.length > 12) {
50 | toast.error("Паролата трябва да е между 6 и 12 символа");
51 | return;
52 | }
53 | if (password !== repass) {
54 | toast.error("Въведените пароли не съвпадат");
55 | return;
56 | }
57 |
58 | const userData = {
59 | name,
60 | email,
61 | password,
62 | };
63 |
64 | // dispatch(register(userData))
65 | };
66 |
67 | if (isLoading) {
68 | return
69 | }
70 |
71 | return (
72 |
159 | );
160 | };
161 |
162 | export default EditProfile;
163 |
--------------------------------------------------------------------------------
/frontend/src/components/profile/MyProfile.js:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import classes from "./MyProfile.module.css";
3 | import { useParams, useNavigate } from "react-router-dom";
4 | import { useState } from "react";
5 |
6 | const MyProfile = () => {
7 | const { user } = useSelector((state) => state.auth);
8 | const navigate = useNavigate();
9 | const params = useParams();
10 | const profileId = params.profileId;
11 | const onUpdate = (e) => {
12 | navigate(`/profile/edit/${user.id}`);
13 | };
14 |
15 | console.log(profileId);
16 | console.log(user);
17 |
18 | return (
19 |
20 |
21 | {profileId === undefined ? (
22 | Здравей, {user.name}
23 | ) : null}
24 | {!user.profilePic ? (
25 |
30 | ) : null}
31 | Имена - {user.name}
32 | Имейл - {user.email}
33 | {!user.profilePic ? (
34 |
35 | Все още не си добавил профилна снимка. Покажи кой се крие за
36 | прекрасните ти рецепти и добави през "РЕДАКТИРАЙ"
37 |
38 | ) : null}
39 |
40 | РЕДАКТИРАЙ
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | export default MyProfile;
48 |
--------------------------------------------------------------------------------
/frontend/src/components/profile/MyProfile.module.css:
--------------------------------------------------------------------------------
1 | .background {
2 | background-image: url("../../assets/fresh-raw-meat.jpg");
3 | min-height: 100vh;
4 | background-size: cover;
5 | background-repeat: no-repeat;
6 | }
7 |
8 | .profile_section {
9 | max-width: 560px;
10 | margin-left: 10%;
11 | padding-top: 4rem;
12 | padding-bottom: 4rem;
13 | }
14 |
15 | .profile_name {
16 | margin-top: 12px;
17 | color: white;
18 | }
19 |
20 | .noPic {
21 | margin: 1rem 0;
22 | color: white;
23 | }
24 |
25 | .profile__pic {
26 | height: 250px;
27 | border-radius: 15px;
28 | }
29 | /*
30 | font-family: 'Caveat', cursive;
31 |
32 | font-family: 'Lobster', cursive;
33 |
34 | font-family: 'Marck Script', cursive;
35 |
36 | font-family: 'Montserrat', sans-serif;
37 |
38 | font-family: 'Nunito', sans-serif;
39 |
40 | font-family: 'Pacifico', cursive;
41 | */
42 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/Comments.js:
--------------------------------------------------------------------------------
1 | import classes from "./Comments.module.css";
2 |
3 | const Comments = ({ comment }) => {
4 | return (
5 |
6 |
{comment.name}
7 |
{comment.comment}
8 |
9 | );
10 | };
11 |
12 | export default Comments;
13 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/Comments.module.css:
--------------------------------------------------------------------------------
1 | .comments__section {
2 | padding: 1rem;
3 | margin: 1rem 2rem;
4 | border-radius: 10px;
5 | background-color: var(--purple);
6 | }
7 |
8 | .first {
9 | font-family: "Marck Script", cursive;
10 | font-size: 30px;
11 | padding-left: 20px;
12 | padding-right: 20px;
13 | padding-top: 40px;
14 | }
15 |
16 | /*
17 | font-family: 'Pacifico', cursive;
18 |
19 | font-family: 'Caveat', cursive;
20 |
21 | font-family: 'Lobster', cursive;
22 |
23 | font-family: 'Marck Script', cursive;
24 |
25 | font-family: 'Montserrat', sans-serif;
26 |
27 | font-family: 'Nunito', sans-serif;
28 | */
29 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/EditRecipe.js:
--------------------------------------------------------------------------------
1 | import classes from "./EditRecipe.module.css";
2 |
3 | import { useParams, useNavigate } from "react-router-dom";
4 | import { useState, useEffect, Fragment } from "react";
5 | import { useSelector, useDispatch } from "react-redux";
6 | import { getSingleRecipe, reset } from "../../features/recipes/recipeSlice";
7 | import { FaPlusCircle } from "react-icons/fa";
8 | import { toast } from "react-toastify";
9 | import Spinner from "../layout/Spinner";
10 | import SingleIngredient from "./SingleIngredient";
11 |
12 | const EditRecipe = () => {
13 | const params = useParams();
14 | const { recipeId } = params;
15 | const [products, setProducts] = useState([]);
16 | const [item, setItem] = useState("");
17 | const [volume, setVolume] = useState("");
18 | const [type, setType] = useState("грама");
19 | const [loading, setLoading] = useState(false);
20 | const [formData, setFormData] = useState({
21 | title: "",
22 | products: [],
23 | preparation: "",
24 | suitableFor: "",
25 | photos: "",
26 | });
27 | const postRecipe = true;
28 | const edit = true;
29 |
30 | const navigate = useNavigate();
31 | const dispatch = useDispatch();
32 |
33 | useEffect(() => {
34 | if (recipeId !== undefined) {
35 | dispatch(getSingleRecipe(recipeId));
36 | }
37 | }, [dispatch, recipeId]);
38 |
39 | const { user } = useSelector((state) => state.auth);
40 | const { recipe, isError, isSuccess, message } = useSelector(
41 | (state) => state.recipe
42 | );
43 |
44 | useEffect(() => {
45 | setFormData((prevState) => ({
46 | ...prevState,
47 | title: recipe.title,
48 | products: recipe.products,
49 | preparation: recipe.preparation,
50 | suitableFor: recipe.suitableFor,
51 | photos: recipe.photos,
52 | }));
53 | }, []);
54 |
55 | useEffect(() => {
56 | setProducts(formData.products);
57 | }, [formData.products]);
58 | // useEffect(() => {
59 | // setOldProducts(formData.products)
60 | // }, [formData.products])
61 |
62 | const { photos, title, preparation, suitableFor } = formData;
63 |
64 | const onItemAdd = (e) => {
65 | setItem(e.target.value);
66 | };
67 |
68 | const onVolumeAdd = (e) => {
69 | setVolume(Number(e.target.value));
70 | };
71 |
72 | const onSelectType = (e) => {
73 | setType(e.target.value);
74 | };
75 |
76 | const onMutate = (e) => {
77 | if (e.target.name === "suitableFor") {
78 | setFormData((prevState) => ({
79 | ...prevState,
80 | [e.target.name]: Number(e.target.value),
81 | }));
82 | } else {
83 | setFormData((prevState) => ({
84 | ...prevState,
85 | [e.target.name]: e.target.value,
86 | }));
87 | }
88 | };
89 |
90 | const onProductsUpdate = (item) => {
91 | const updatedProducts = products.filter((product) => product.item !== item);
92 |
93 | setProducts(updatedProducts);
94 | setFormData((prevState) => ({
95 | ...prevState,
96 | products: updatedProducts,
97 | }));
98 | };
99 |
100 | const onProductAdd = (e) => {
101 | e.preventDefault();
102 |
103 | const newProduct = {
104 | item,
105 | volume,
106 | type,
107 | };
108 |
109 | setProducts((prevState) => [...prevState, newProduct]);
110 | setFormData((prevState) => ({
111 | ...prevState,
112 | products: products,
113 | }));
114 | setItem("");
115 | setType("грама");
116 | setVolume("");
117 | };
118 |
119 | const handlePhotoUpload = (e) => {
120 | setFormData((prevState) => ({
121 | ...prevState,
122 | photos: e.target.files[0],
123 | }));
124 | };
125 |
126 | useEffect(() => {
127 | if (isError) {
128 | toast.error(message);
129 | }
130 | // if (isSuccess) {
131 | // dispatch(reset());
132 | // navigate("/recepti");
133 | // }
134 | dispatch(reset());
135 | }, [message, isError, isSuccess, navigate, dispatch]);
136 |
137 | const onSubmit = async (e) => {
138 | e.preventDefault();
139 | setLoading(true);
140 |
141 | const formData = new FormData();
142 | formData.append("title", title);
143 | formData.append("products", JSON.stringify(products));
144 | formData.append("preparation", preparation);
145 | formData.append("suitableFor", suitableFor);
146 | formData.append("photos", photos);
147 | console.log(user);
148 | console.log(formData);
149 | try {
150 | const response = await fetch(
151 | `https://cook-master.onrender.com/api/posts/${recipeId}`,
152 | {
153 | method: "PUT",
154 | body: formData,
155 | headers: {
156 | Authorization: `Bearer ${user.token}`,
157 | // "Content-Type": "multipart/form-data: boundary=XXX",
158 | },
159 | }
160 | );
161 |
162 | if (response.status === 200) {
163 | const updatedRecipe = await response.json();
164 | setLoading(false);
165 | navigate("/");
166 | return updatedRecipe;
167 | }
168 | } catch (error) {
169 | const message =
170 | (error.response &&
171 | error.response.data &&
172 | error.response.data.message) ||
173 | error.message ||
174 | error.toString();
175 | console.log(message);
176 | }
177 | };
178 |
179 | if (loading) {
180 | return ;
181 | }
182 |
183 | return (
184 |
185 |
186 |
187 |
188 |
189 |
Здравей, {user.name}
190 |
191 | Намерил си начин да подобриш вкуса на своята рецепта? Добави
192 | промените оттук!
193 |
194 |
195 |
196 |
341 |
342 |
343 |
344 |
345 | );
346 | };
347 |
348 | export default EditRecipe;
349 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/EditRecipe.module.css:
--------------------------------------------------------------------------------
1 | .background {
2 | background-image: url("../../assets/chushka.jpg");
3 | background-repeat: no-repeat;
4 | background-size: cover;
5 | min-height: 110vh;
6 | padding-bottom: 4rem;
7 | }
8 |
9 | .content {
10 | max-width: 720px;
11 | margin-left: 15%;
12 | }
13 |
14 | .title {
15 | font-family: "Josefin Sans", sans-serif;
16 | text-align: center;
17 | padding-top: 80px;
18 | color: white;
19 | font-size: 20px;
20 | letter-spacing: 2px;
21 | }
22 |
23 | .formInside {
24 | margin-top: 40px;
25 | padding: 30px;
26 | display: flex;
27 | flex-direction: column;
28 | gap: 15px;
29 | }
30 |
31 | .textArea {
32 | background-color: rgba(105, 90, 205, 0.7);
33 | border: none;
34 | color: white;
35 | border-radius: 15px;
36 | padding: 12px;
37 | font-size: 16px;
38 | outline: none;
39 | }
40 |
41 | .white {
42 | color: white;
43 | }
44 |
45 | .categories {
46 | display: flex;
47 | justify-content: center;
48 | gap: 80px;
49 | }
50 |
51 | .checkboxItems {
52 | display: flex;
53 | gap: 15px;
54 | }
55 |
56 | .checkbox {
57 | font-family: "Josefin Sans", sans-serif;
58 | font-weight: 500;
59 | color: white;
60 | font-size: 18px;
61 | list-style-type: none;
62 | }
63 |
64 | .suitable {
65 | display: flex;
66 | flex-direction: row;
67 | gap: 1rem;
68 | align-items: baseline;
69 | justify-content: center;
70 | }
71 |
72 | .suitableSelect {
73 | width: 50px;
74 | }
75 | .addIngredient {
76 | padding: 10px;
77 | border: none;
78 | border-radius: 50%;
79 | background-color: var(--violet);
80 | color: black;
81 | cursor: pointer;
82 | }
83 |
84 | .ingredient {
85 | display: flex;
86 | justify-content: space-between;
87 | gap: 1rem;
88 | align-items: baseline;
89 | }
90 |
91 | .upload {
92 | color: black;
93 | font-family: "Nunito", sans-serif;
94 | font-weight: 700;
95 | text-transform: uppercase;
96 | background-color: var(--purple);
97 | transition: 300ms ease-in-out;
98 | padding: 12px;
99 | cursor: pointer;
100 | border-radius: 15px;
101 | }
102 |
103 | .upload__button {
104 | margin: 1rem auto;
105 | }
106 |
107 | .upload:hover {
108 | background-color: var(--light-purple);
109 | }
110 | /*
111 | font-family: 'Caveat', cursive;
112 |
113 | font-family: 'Lobster', cursive;
114 |
115 | font-family: 'Marck Script', cursive;
116 |
117 | font-family: 'Montserrat', sans-serif;
118 |
119 | font-family: 'Nunito', sans-serif;
120 |
121 | font-family: 'Pacifico', cursive;
122 | */
123 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/PostRecipe.js:
--------------------------------------------------------------------------------
1 | import classes from "./PostRecipe.module.css";
2 | import { Fragment, useState, useEffect } from "react";
3 | import SingleIngredient from "./SingleIngredient";
4 | import { useNavigate, useParams } from "react-router-dom";
5 | import { toast } from "react-toastify";
6 | import Spinner from "../layout/Spinner";
7 | import { useSelector, useDispatch } from "react-redux";
8 | import { createRecipe, reset } from "../../features/recipes/recipeSlice";
9 | import { FaPlusCircle } from "react-icons/fa";
10 |
11 | const PostRecipe = () => {
12 | const [products, setProducts] = useState([]);
13 | const [item, setItem] = useState("");
14 | const [volume, setVolume] = useState("");
15 | const [type, setType] = useState("грама");
16 | const [loading, setLoading] = useState(false);
17 | const editMode = true;
18 | const postRecipe = true;
19 | const navigate = useNavigate();
20 | const dispatch = useDispatch();
21 |
22 | const { user } = useSelector((state) => state.auth);
23 | const { recipe, isError, isSuccess, message } = useSelector(
24 | (state) => state.recipe
25 | );
26 |
27 | // useEffect(() => {
28 | // if (recipeId !== undefined) {
29 | // dispatch(getSingleRecipe(recipeId));
30 | // }
31 | // }, [dispatch, recipeId]);
32 |
33 | const [formData, setFormData] = useState({
34 | title: "",
35 | products: [],
36 | preparation: "",
37 | suitableFor: "",
38 | photos: "",
39 | });
40 |
41 | const { photos, title, preparation, suitableFor } = formData;
42 |
43 | const onItemAdd = (e) => {
44 | setItem(e.target.value);
45 | };
46 |
47 | const onVolumeAdd = (e) => {
48 | setVolume(Number(e.target.value));
49 | };
50 |
51 | const onSelectType = (e) => {
52 | setType(e.target.value);
53 | };
54 |
55 | const onMutate = (e) => {
56 | if (e.target.name === "suitableFor") {
57 | setFormData((prevState) => ({
58 | ...prevState,
59 | [e.target.name]: Number(e.target.value),
60 | }));
61 | } else {
62 | setFormData((prevState) => ({
63 | ...prevState,
64 | [e.target.name]: e.target.value,
65 | }));
66 | }
67 | };
68 |
69 | const onProductsUpdate = (item) => {
70 | setProducts(products.filter((product) => product.item !== item));
71 | };
72 |
73 | const onProductAdd = (e) => {
74 | e.preventDefault();
75 |
76 | const newProduct = {
77 | item,
78 | volume,
79 | type,
80 | };
81 |
82 | setProducts((prevState) => [...prevState, newProduct]);
83 | setFormData((prevState) => ({
84 | ...prevState,
85 | products: products,
86 | }));
87 | setItem("");
88 | setType("грама");
89 | setVolume("");
90 | };
91 |
92 | const handlePhotoUpload = (e) => {
93 | // setUploadedPhotos((prevState) => [...prevState, e.target.files[0]]);
94 | setFormData((prevState) => ({
95 | ...prevState,
96 | photos: e.target.files[0],
97 | }));
98 | };
99 |
100 | useEffect(() => {
101 | if (isError) {
102 | toast.error(message);
103 | }
104 | // if (isSuccess) {
105 | // dispatch(reset());
106 | // navigate("/recepti");
107 | // }
108 | dispatch(reset());
109 | }, [message, isError, isSuccess, navigate, dispatch]);
110 |
111 | const onSubmit = async (e) => {
112 | e.preventDefault();
113 | setLoading(true);
114 |
115 | const formData = new FormData();
116 | formData.append("title", title);
117 | formData.append("products", JSON.stringify(products));
118 | formData.append("preparation", preparation);
119 | formData.append("suitableFor", suitableFor);
120 | formData.append("photos", photos);
121 |
122 | try {
123 | const response = await fetch(
124 | `https://cook-master.onrender.com/api/posts`,
125 | {
126 | method: "POST",
127 | body: formData,
128 | headers: {
129 | Authorization: `Bearer ${user.token}`,
130 | // "Content-Type": "multipart/form-data: boundary=XXX",
131 | },
132 | }
133 | );
134 |
135 | if (response.status === 201) {
136 | const addedRecipe = await response.json();
137 | setLoading(false);
138 | navigate("/");
139 | return addedRecipe;
140 | }
141 | } catch (error) {
142 | const message =
143 | (error.response &&
144 | error.response.data &&
145 | error.response.data.message) ||
146 | error.message ||
147 | error.toString();
148 | console.log(message);
149 | }
150 | };
151 |
152 | if (loading) {
153 | return ;
154 | }
155 |
156 | return (
157 |
158 |
159 |
160 |
161 |
162 |
Здравей, {user.name}
163 | Добави своята страхотна рецепта!
164 |
165 |
166 |
171 |
181 | Съставки:
182 | {products.length > 0
183 | ? products.map((product) => (
184 |
192 | ))
193 | : null}
194 |
195 |
203 |
211 |
217 | грама
218 | мл.
219 | брой(я)
220 | На вкус
221 |
222 |
226 |
227 |
228 |
229 |
230 |
231 | Добави стъпките за приготвянето на твоя шедьовър
232 |
233 |
243 |
244 | {/* Добави категории
245 |
246 |
251 | */}
252 |
253 |
254 |
Подходяща за
255 |
263 | 1
264 | 2
265 | 4
266 | 6
267 |
268 |
269 |
човека.
270 |
271 | {/*
272 | Подходяща за
273 |
274 |
281 | 1
282 | 2
283 | 4
284 | 6
285 |
286 |
287 | човека.
288 |
*/}
289 |
290 |
291 |
292 | Добави снимки
293 |
294 |
304 |
305 |
306 | ДОБАВИ РЕЦЕПТА
307 |
308 |
309 |
310 |
311 |
312 |
313 | );
314 | };
315 |
316 | export default PostRecipe;
317 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/PostRecipe.module.css:
--------------------------------------------------------------------------------
1 | .background {
2 | background-image: url("../../assets/chushka.jpg");
3 | background-repeat: no-repeat;
4 | background-size: cover;
5 | min-height: 110vh;
6 | padding-bottom: 4rem;
7 | }
8 |
9 | .content {
10 | max-width: 720px;
11 | margin-left: 15%;
12 | }
13 |
14 | .title {
15 | text-align: center;
16 | padding-top: 80px;
17 | color: white;
18 | font-size: 24px;
19 | letter-spacing: 2px;
20 | }
21 |
22 | .formInside {
23 | margin-top: 20px;
24 | padding: 30px;
25 | display: flex;
26 | flex-direction: column;
27 | gap: 10px;
28 | }
29 |
30 | .categories {
31 | display: flex;
32 | justify-content: center;
33 | gap: 80px;
34 | }
35 |
36 | .checkboxItems {
37 | display: flex;
38 | gap: 15px;
39 | }
40 |
41 | .checkbox {
42 | font-weight: 500;
43 | color: white;
44 | font-size: 18px;
45 | list-style-type: none;
46 | }
47 |
48 | .suitable {
49 | display: flex;
50 | flex-direction: row;
51 | gap: 1rem;
52 | align-items: baseline;
53 | justify-content: center;
54 | }
55 |
56 | .suitableSelect {
57 | width: 50px;
58 | }
59 | .addIngredient {
60 | padding: 10px;
61 | border: none;
62 | border-radius: 50%;
63 | background-color: var(--violet);
64 | color: black;
65 | cursor: pointer;
66 | }
67 |
68 | .ingredient {
69 | display: flex;
70 | justify-content: space-between;
71 | gap: 1rem;
72 | align-items: baseline;
73 | }
74 |
75 | .fileUp {
76 | visibility: hidden;
77 | }
78 |
79 | .textArea {
80 | background-color: rgba(105, 90, 205, 0.7);
81 | border: none;
82 | color: white;
83 | border-radius: 15px;
84 | padding: 12px;
85 | font-size: 16px;
86 | outline: none;
87 | }
88 |
89 | .upload {
90 | color: black;
91 | font-family: "Nunito", sans-serif;
92 | font-weight: 700;
93 | text-transform: uppercase;
94 | background-color: var(--purple);
95 | transition: 300ms ease-in-out;
96 | padding: 12px;
97 | cursor: pointer;
98 | border-radius: 15px;
99 | }
100 |
101 | .upload__button {
102 | margin: 1rem auto;
103 | }
104 |
105 | .upload:hover {
106 | background-color: var(--light-purple);
107 | }
108 |
109 | .white {
110 | color: white;
111 | }
112 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/Recipes.js:
--------------------------------------------------------------------------------
1 | import classes from "./Recipes.module.css";
2 |
3 | import Spinner from "../layout/Spinner";
4 |
5 | import { useSelector, useDispatch } from "react-redux";
6 | import { useNavigate } from "react-router-dom";
7 | import { useEffect, useState } from "react";
8 | import { toast } from "react-toastify";
9 | import { getAllRecipes, reset } from "../../features/recipes/recipeSlice";
10 |
11 | import SmallRecipeItem from "./SmallRecipeItem";
12 |
13 | const Recipes = () => {
14 | const dispatch = useDispatch();
15 | const { recipes, isSuccess, isLoading, isError, message } = useSelector(
16 | (state) => state.recipe
17 | );
18 |
19 | useEffect(() => {
20 | return () => {
21 | if (isSuccess) {
22 | dispatch(reset());
23 | }
24 | };
25 | }, [isSuccess, dispatch]);
26 |
27 | useEffect(() => {
28 | if (isError) {
29 | toast.error(message);
30 | }
31 |
32 | dispatch(getAllRecipes());
33 | // eslint-disable-next-line
34 | }, [isError, message]);
35 |
36 | if (isLoading) {
37 | return ;
38 | }
39 |
40 | console.log(recipes);
41 |
42 | return (
43 |
44 | {recipes.map((recipe) => (
45 |
46 |
50 |
51 | ))}
52 |
53 | );
54 | };
55 |
56 | export default Recipes;
57 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/Recipes.module.css:
--------------------------------------------------------------------------------
1 | .background {
2 | background-image: url("../../assets/healthy-diet.jpg");
3 | min-height: 110vh;
4 | background-repeat: no-repeat;
5 | background-size: cover;
6 | }
7 |
8 | .recipeBlock {
9 | display: flex;
10 | flex-direction: row;
11 | }
--------------------------------------------------------------------------------
/frontend/src/components/recipes/SingleIngredient.js:
--------------------------------------------------------------------------------
1 | import classes from "./SingleIngredient.module.css";
2 | import { FaMinus } from "react-icons/fa";
3 |
4 | const SingleIngredient = ({
5 | product,
6 | products,
7 | onProductsUpdate,
8 | edit,
9 | postRecipe,
10 | }) => {
11 | const onDelete = (e) => {
12 | onProductsUpdate(product.item);
13 | };
14 | return (
15 |
16 |
19 | {product.item} - {product.volume} {product.type}
20 |
21 | {edit ? (
22 |
23 |
24 |
25 | ) : null}
26 |
27 | );
28 | };
29 |
30 | export default SingleIngredient;
31 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/SingleIngredient.module.css:
--------------------------------------------------------------------------------
1 | .center {
2 | display: flex;
3 | justify-content: space-between;
4 | margin: 0.2rem;
5 | }
6 |
7 | .centered {
8 | display: flex;
9 | justify-content: center;
10 | }
11 |
12 | .product_name {
13 | font-family: "Marck Script", cursive;
14 | font-size: 30px;
15 | color: black;
16 | font-size: 20px;
17 | letter-spacing: 1px;
18 | }
19 |
20 | .white {
21 | color: white;
22 | }
23 |
24 | .remove {
25 | padding-left: 2rem;
26 | }
27 |
28 | .addIngredient {
29 | padding: 8px;
30 | border: none;
31 | border-radius: 50%;
32 | background-color: var(--pinky);
33 | color: black;
34 | cursor: pointer;
35 | }
36 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/SingleRecipe.js:
--------------------------------------------------------------------------------
1 | import classes from "./SingleRecipe.module.css";
2 | import Spinner from "../layout/Spinner";
3 | //import Button from "../layout/Button";
4 |
5 | import { useSelector, useDispatch } from "react-redux";
6 | import { useNavigate, useParams } from "react-router-dom";
7 | import { useEffect, useState } from "react";
8 | import { toast } from "react-toastify";
9 | import {
10 | getSingleRecipe,
11 | reset,
12 | deleteRes,
13 | } from "../../features/recipes/recipeSlice";
14 | import {
15 | addComment,
16 | getAllComments,
17 | } from "../../features/recipes/commentSlice";
18 | import SingleIngredient from "./SingleIngredient";
19 | import Comments from "./Comments";
20 | //import Line from "../layout/Line";
21 |
22 | const SingleRecipe = () => {
23 | const dispatch = useDispatch();
24 | const navigate = useNavigate();
25 | const { id } = useParams();
26 | const [ingredients, setIngredient] = useState([]);
27 | const [activeUser, setActiveUser] = useState(false);
28 | const [newComment, setNewComment] = useState("");
29 | const editMode = false;
30 |
31 | const { user } = useSelector((state) => state.auth);
32 | const { recipe, isSuccess, isLoading, isError, message } = useSelector(
33 | (state) => state.recipe
34 | );
35 | const { comments, isCommentLoading } = useSelector((state) => state.comment);
36 | const { products } = recipe;
37 |
38 | useEffect(() => {
39 | dispatch(getAllComments(id));
40 | // eslint-disable-next-line
41 | }, [isCommentLoading]);
42 |
43 | console.log(comments);
44 |
45 | useEffect(() => {
46 | if (user) {
47 | setActiveUser(user);
48 | }
49 | }, [user]);
50 |
51 | useEffect(() => {
52 | setIngredient(products);
53 | }, [products]);
54 |
55 | useEffect(() => {
56 | return () => {
57 | if (isSuccess) {
58 | dispatch(reset());
59 | }
60 | };
61 | }, [isSuccess, dispatch]);
62 |
63 | useEffect(() => {
64 | if (isError) {
65 | toast.error(message);
66 | }
67 |
68 | dispatch(getSingleRecipe(id));
69 | // eslint-disable-next-line
70 | }, [isError, message, id]);
71 |
72 | const isOwner = activeUser.id === recipe.user;
73 |
74 | const onDelete = (e) => {
75 | if (isOwner) {
76 | dispatch(deleteRes(id));
77 | if (isSuccess) {
78 | navigate("/");
79 | }
80 | }
81 | };
82 |
83 | const onUpdate = () => {
84 | navigate(`/dobavi/${id}`);
85 | };
86 |
87 | const onCommentHandler = (e) => {
88 | setNewComment(e.target.value);
89 | };
90 |
91 | const onSubmit = (e) => {
92 | e.preventDefault();
93 |
94 | if (newComment.length < 5) {
95 | toast.error("Коментарът трябва да е минимум 5 символа");
96 | return;
97 | }
98 |
99 | const commentData = {
100 | recipeId: id,
101 | name: user.name,
102 | comment: newComment,
103 | };
104 | dispatch(addComment(commentData));
105 | setNewComment("");
106 | };
107 |
108 | if (isLoading) {
109 | return ;
110 | }
111 |
112 | return (
113 |
114 |
115 |
116 |
{recipe.title}
117 |
118 | {recipe.photos !== undefined ? (
119 |
120 |
125 |
126 | ) : null}
127 |
128 |
Необходими продукти
129 |
130 | {ingredients !== undefined
131 | ? ingredients.map((ingredient) => (
132 |
137 | ))
138 | : null}
139 |
140 |
141 |
{recipe.preparation}
142 |
143 |
144 | {/*
145 |
146 |
*/}
147 | {isOwner ? (
148 |
149 |
150 | РЕДАКТИРАЙ
151 |
152 |
156 | ИЗТРИЙ
157 |
158 |
159 | ) : null}
160 |
161 |
162 |
163 |
Коментари
164 | {comments.length > 0 ? (
165 | comments.map((comment) => (
166 |
167 |
168 |
169 | ))
170 | ) : (
171 |
Все още няма нито един коментар към тази рецепта
172 | )}
173 | {user ? (
174 |
175 |
176 |
186 |
187 |
188 |
189 | Добави коментар
190 |
191 |
192 | ) : null}
193 |
194 |
195 |
196 | );
197 | };
198 |
199 | export default SingleRecipe;
200 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/SingleRecipe.module.css:
--------------------------------------------------------------------------------
1 | .background {
2 | background-image: url("../../assets/healthy-dietwebp.webp");
3 | min-height: 110vh;
4 | background-repeat: no-repeat;
5 | background-size: cover;
6 | }
7 |
8 | .center {
9 | padding: 2rem;
10 | margin: auto;
11 | max-width: 720px;
12 | text-align: center;
13 | }
14 |
15 | .card {
16 | margin-top: 20px;
17 | padding-top: 2rem;
18 | border: 10px double var(--deep-purple);
19 | border-radius: 20px;
20 | -webkit-box-shadow: 5px 5px 15px 5px #000000;
21 | box-shadow: 5px 5px 15px 5px #000000;
22 | background-color: var(--light-purple-transparent);
23 | }
24 |
25 | .title {
26 | font-size: 40px;
27 | margin-top: 20px;
28 | }
29 |
30 | .product {
31 | margin-top: 28px;
32 | margin-bottom: 18px;
33 | }
34 |
35 | .singlePhoto {
36 | margin-top: 40px;
37 | max-width: 300px;
38 | max-height: 300px;
39 | border: 10px double rgba(92, 92, 92, 0.5);
40 | border-radius: 20px;
41 | -webkit-box-shadow: 5px 5px 15px 5px #000000;
42 | box-shadow: 5px 5px 15px 5px #000000;
43 | }
44 |
45 | .preparation {
46 | padding-left: 24px;
47 | padding-right: 24px;
48 | margin-top: 28px;
49 | margin-bottom: 28px;
50 | }
51 |
52 | .preparation p {
53 | text-align: left;
54 | }
55 |
56 | .buttons {
57 | display: flex;
58 | justify-content: space-between;
59 | margin-top: 20px;
60 | margin-bottom: 40px;
61 | padding-left: 40px;
62 | padding-right: 40px;
63 | }
64 |
65 | .btn_warn {
66 | background-color: var(--pinky);
67 | }
68 |
69 | .btn_warn:hover {
70 | background-color: pink;
71 | }
72 |
73 | .form__button {
74 | display: flex;
75 | justify-content: center;
76 | margin-bottom: 2rem;
77 | }
78 |
79 | .textArea {
80 | background-color: rgba(105, 90, 205, 0.7);
81 | border: none;
82 | color: white;
83 | border-radius: 15px;
84 | padding: 12px;
85 | font-size: 22px;
86 | min-width: 350px;
87 | outline: none;
88 | font-family: "Marck Script", cursive;
89 | }
90 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/SmallRecipeItem.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import classes from "./SmallRecipeItem.module.css";
4 | import Button from "../layout/Button";
5 | import Line from "../layout/Line";
6 |
7 | const SmallRecipeItem = ({ recipe }) => {
8 | const recipeExcerpt = recipe.preparation.substring(0, 150) + "...";
9 | const user = recipe.user;
10 |
11 | return (
12 |
13 |
14 |
19 |
20 |
{recipe.title}
21 |
22 | {/*
Виж */}
23 |
{recipeExcerpt}
24 |
25 |
26 | ВИЖ ПОВЕЧЕ
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default SmallRecipeItem;
36 |
--------------------------------------------------------------------------------
/frontend/src/components/recipes/SmallRecipeItem.module.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::before,
3 | *::after {
4 | box-sizing: border-box;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | .recipesList {
10 | background-color: rgba(240, 248, 255, 0.5);
11 | min-height: 100px;
12 | max-width: 720px;
13 | padding: 28px;
14 | display: flex;
15 | gap: 2rem;
16 | align-items: center;
17 | margin: auto;
18 | margin-top: 4rem;
19 | margin-bottom: 8rem;
20 | border: 10px double rgba(92, 92, 92, 0.5);
21 | border-radius: 20px;
22 | -webkit-box-shadow: 5px 5px 15px 5px #000000;
23 | box-shadow: 5px 5px 15px 5px #000000;
24 | opacity: 1;
25 | }
26 |
27 | .smallPic {
28 | -webkit-box-shadow: 0px 0px 10px 3px rgba(0, 0, 0, 0.5);
29 | box-shadow: 0px 0px 10px 3px rgba(0, 0, 0, 0.5);
30 | border-radius: 20px;
31 | }
32 |
33 | .recipesList img {
34 | max-height: 150px;
35 | }
36 |
37 | .btnDiv {
38 | margin-top: 20px;
39 | }
40 |
41 | .btn:hover {
42 | background-color: rgb(86, 168, 5);
43 | color: black;
44 | cursor: pointer;
45 | }
46 |
47 | .btnWarning {
48 | background-color: orange;
49 | }
50 | /*
51 | font-family: 'Pacifico', cursive;
52 |
53 | font-family: 'Caveat', cursive;
54 |
55 | font-family: 'Lobster', cursive;
56 |
57 | font-family: 'Marck Script', cursive;
58 |
59 | font-family: 'Montserrat', sans-serif;
60 |
61 | font-family: 'Nunito', sans-serif;
62 |
63 |
64 | */
65 |
--------------------------------------------------------------------------------
/frontend/src/features/auth/authService.js:
--------------------------------------------------------------------------------
1 | // REGISTER USER
2 | const register = async (userData) => {
3 | const response = await fetch(
4 | `https://cook-master-backend.onrender.com/api/users`,
5 | {
6 | method: "POST",
7 | body: JSON.stringify(userData),
8 | headers: {
9 | "Content-Type": "application/json",
10 | },
11 | }
12 | );
13 |
14 | if (response.status === 400) {
15 | throw new Error("Акант с такъв имейл вече съществува");
16 | }
17 |
18 | if (response.status === 201) {
19 | const user = await response.json();
20 | localStorage.setItem("user", JSON.stringify(user));
21 | return user;
22 | }
23 | };
24 |
25 | // LOGIN user
26 | const login = async (userData) => {
27 | const response = await fetch(
28 | `https://cook-master-backend.onrender.com/api/users/login`,
29 | {
30 | method: "POST",
31 | body: JSON.stringify(userData),
32 | headers: {
33 | "Content-Type": "application/json",
34 | },
35 | }
36 | );
37 |
38 | if (response.status === 401) {
39 | throw new Error("Невалидни имейл или парола");
40 | }
41 |
42 | if (response.status === 200) {
43 | const user = await response.json();
44 | localStorage.setItem("user", JSON.stringify(user));
45 | return user;
46 | }
47 | };
48 |
49 | // LOGOUT user
50 | const logout = () => localStorage.removeItem("user");
51 |
52 | const authService = {
53 | register,
54 | login,
55 | logout,
56 | };
57 |
58 | export default authService;
59 |
--------------------------------------------------------------------------------
/frontend/src/features/auth/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
2 | import authService from "./authService";
3 |
4 | // GET user from localstorage
5 | const user = JSON.parse(localStorage.getItem('user'))
6 |
7 | const initialState = {
8 | user: user ? user : null,
9 | isError: false,
10 | isSuccess: false,
11 | isLoading: false,
12 | message: "",
13 | };
14 |
15 | // REGISTER new user
16 | export const register = createAsyncThunk(
17 | "auth/register",
18 | async (user, thunkAPI) => {
19 | try {
20 | return await authService.register(user);
21 | } catch (error) {
22 | const message =
23 | (error.response &&
24 | error.response.data &&
25 | error.response.data.message) ||
26 | error.message ||
27 | error.toString();
28 |
29 | return thunkAPI.rejectWithValue(message);
30 | }
31 | }
32 | );
33 |
34 | // LOGIN user
35 | export const login = createAsyncThunk("auth/login", async (user, thunkAPI) => {
36 | try {
37 | return await authService.login(user);
38 | } catch (error) {
39 | const message =
40 | (error.response && error.response.data && error.response.data.message) ||
41 | error.message ||
42 | error.toString();
43 |
44 | return thunkAPI.rejectWithValue(message);
45 | }
46 | });
47 |
48 | // LOGOUT user
49 | export const logout = createAsyncThunk('auth/logout', async() => {
50 | await authService.logout()
51 | })
52 |
53 | export const authSlice = createSlice({
54 | name: "auth",
55 | initialState,
56 | reducers: {
57 | reset: (state) => {
58 | state.isLoading = false;
59 | state.isError = false;
60 | state.isSuccess = false;
61 | state.message = "";
62 | },
63 | },
64 | extraReducers: (builder) => {
65 | builder
66 | .addCase(register.pending, (state) => {
67 | state.isLoading = true;
68 | })
69 | .addCase(register.fulfilled, (state, action) => {
70 | state.isLoading = false;
71 | state.isSuccess = true;
72 | state.user = action.payload;
73 | })
74 | .addCase(register.rejected, (state, action) => {
75 | state.isLoading = false;
76 | state.isError = true;
77 | state.message = action.payload;
78 | state.user = null;
79 | })
80 | .addCase(logout.fulfilled, (state) => {
81 | state.user = null;
82 | })
83 | .addCase(login.pending, (state) => {
84 | state.isLoading = true;
85 | })
86 | .addCase(login.fulfilled, (state, action) => {
87 | state.isLoading = false;
88 | state.isSuccess = true;
89 | state.user = action.payload;
90 | })
91 | .addCase(login.rejected, (state, action) => {
92 | state.isLoading = false;
93 | state.isError = true;
94 | state.message = action.payload;
95 | state.user = null;
96 | })
97 | },
98 | });
99 |
100 | export const { reset } = authSlice.actions;
101 | export default authSlice.reducer;
102 |
--------------------------------------------------------------------------------
/frontend/src/features/recipes/commentService.js:
--------------------------------------------------------------------------------
1 | // POST comment
2 | const addComment = async (comment, token) => {
3 | const response = await fetch(
4 | `https://cook-master-backend.onrender.com/api/posts/comments`,
5 | {
6 | method: "POST",
7 | body: JSON.stringify(comment),
8 | headers: {
9 | Authorization: `Bearer ${token}`,
10 | "Content-Type": "application/json",
11 | },
12 | }
13 | );
14 |
15 | if (response.status === 201) {
16 | const addedComment = await response.json();
17 | return addedComment;
18 | }
19 | };
20 |
21 | // GET comments
22 | const getAllComments = async (id) => {
23 | const response = await fetch(
24 | `https://cook-master-backend.onrender.com/api/posts/${id}/comments`
25 | );
26 |
27 | if (response.status === 200) {
28 | const comments = await response.json();
29 | return comments;
30 | }
31 | };
32 |
33 | const commentService = {
34 | addComment,
35 | getAllComments,
36 | };
37 |
38 | export default commentService;
39 |
--------------------------------------------------------------------------------
/frontend/src/features/recipes/commentSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
2 | import commentService from "./commentService";
3 |
4 | const initialState = {
5 | comments: [],
6 | isCommentError: false,
7 | isCommentSuccess: false,
8 | isCommentLoading: false,
9 | messageComment: "",
10 | };
11 |
12 | // ADD new comment
13 | export const addComment = createAsyncThunk(
14 | "recipe/comment",
15 | async (comment, thunkAPI) => {
16 | try {
17 | const token = thunkAPI.getState().auth.user.token;
18 | return await commentService.addComment(comment, token);
19 | } catch (error) {
20 | const message =
21 | (error.response &&
22 | error.response.data &&
23 | error.response.data.message) ||
24 | error.messageComment ||
25 | error.toString();
26 |
27 | return thunkAPI.rejectWithValue(message);
28 | }
29 | }
30 | );
31 |
32 | // GET all comments
33 | export const getAllComments = createAsyncThunk(
34 | "recipe/getAllComments",
35 | async (id, thunkAPI) => {
36 | try {
37 | return await commentService.getAllComments(id);
38 | } catch (error) {
39 | const message =
40 | (error.response &&
41 | error.response.data &&
42 | error.response.data.message) ||
43 | error.messageComment ||
44 | error.toString();
45 |
46 | return thunkAPI.rejectWithValue(message);
47 | }
48 | }
49 | );
50 |
51 | export const commentSlice = createSlice({
52 | name: "comment",
53 | initialState,
54 | reducers: {
55 | reset: (state) => initialState,
56 | },
57 | extraReducers: (builder) => {
58 | builder
59 | // Create comment
60 | .addCase(addComment.pending, (state) => {
61 | state.isCommentLoading = true;
62 | })
63 | .addCase(addComment.fulfilled, (state) => {
64 | state.isCommentLoading = false;
65 | state.isCommentSuccess = true;
66 | })
67 | .addCase(addComment.rejected, (state, action) => {
68 | state.isCommentLoading = false;
69 | state.isCommentError = true;
70 | state.messageComment = action.payload;
71 | })
72 | // GET all comments/ public
73 | .addCase(getAllComments.pending, (state) => {
74 | state.isCommentLoading = true;
75 | })
76 | .addCase(getAllComments.fulfilled, (state, action) => {
77 | state.isLoading = false;
78 | state.isCommentSuccess = true;
79 | state.comments = action.payload;
80 | })
81 | .addCase(getAllComments.rejected, (state, action) => {
82 | state.isLoading = false;
83 | state.isCommentError = true;
84 | state.messageComment = action.payload;
85 | });
86 | },
87 | });
88 |
89 | export const { reset } = commentSlice.actions;
90 | export default commentSlice.reducer;
91 |
--------------------------------------------------------------------------------
/frontend/src/features/recipes/recipeService.js:
--------------------------------------------------------------------------------
1 | // CREATE recipe
2 | const addRecipe = async (recipe, token) => {
3 | const response = await fetch(
4 | `https://cook-master-backend.onrender.com/api/posts`,
5 | {
6 | method: "POST",
7 | body: JSON.stringify(recipe),
8 | headers: {
9 | Authorization: `Bearer ${token}`,
10 | },
11 | }
12 | );
13 |
14 | if (response.status === 201) {
15 | const addedRecipe = await response.json();
16 | return addedRecipe;
17 | }
18 | };
19 |
20 | // GET My recipes
21 | const getMine = async (token) => {
22 | const response = await fetch(
23 | `https://cook-master-backend.onrender.com/api/posts/myRecipes`,
24 | {
25 | method: "GET",
26 | headers: {
27 | Authorization: `Bearer ${token}`,
28 | "Content-Type": "application/json",
29 | },
30 | }
31 | );
32 |
33 | if (response.status === 200) {
34 | const recipes = await response.json();
35 | return recipes;
36 | }
37 | };
38 |
39 | // GET All recipes
40 | const getAllRecipes = async () => {
41 | const response = await fetch(
42 | `https://cook-master-backend.onrender.com/api/posts`
43 | );
44 |
45 | if (response.status === 200) {
46 | const recipes = await response.json();
47 | return recipes;
48 | }
49 | };
50 |
51 | // GET Last Three recipes
52 | const getLastThree = async () => {
53 | const response = await fetch(
54 | `https://cook-master-backend.onrender.com/api/posts/lastThree`
55 | );
56 |
57 | if (response.status === 200) {
58 | const recipes = await response.json();
59 | return recipes;
60 | }
61 | };
62 |
63 | // GET Single Recipe
64 | const getSingle = async (recipeId) => {
65 | const response = await fetch(
66 | `https://cook-master-backend.onrender.com/api/posts/` + recipeId
67 | );
68 |
69 | if (response.status === 200) {
70 | const recipe = await response.json();
71 | return recipe;
72 | }
73 | };
74 |
75 | // DELETE a recipe
76 | const deleteRecipe = async (recipeId, token) => {
77 | const response = await fetch(
78 | `https://cook-master-backend.onrender.com/api/posts/` + recipeId,
79 | {
80 | method: "DELETE",
81 | headers: {
82 | Authorization: `Bearer ${token}`,
83 | "Content-Type": "application/json",
84 | },
85 | }
86 | );
87 |
88 | if (response.status === 200) {
89 | return response.message;
90 | }
91 | };
92 |
93 | // UPDATE a recipe
94 | const updateRecipe = async (recipe, recipeId, token) => {
95 | const response = await fetch(
96 | `https://cook-master-backend.onrender.com/api/posts/` + recipeId,
97 | {
98 | method: "PUT",
99 | body: JSON.stringify(recipe),
100 | headers: {
101 | Authorization: `Bearer ${token}`,
102 | "Content-Type": "application/json",
103 | },
104 | }
105 | );
106 |
107 | if (response.status === 201) {
108 | const updatedRecipe = await response.json();
109 | return updatedRecipe;
110 | }
111 | };
112 |
113 | const recipeService = {
114 | addRecipe,
115 | getMine,
116 | getAllRecipes,
117 | getSingle,
118 | deleteRecipe,
119 | updateRecipe,
120 | getLastThree,
121 | };
122 |
123 | export default recipeService;
124 |
--------------------------------------------------------------------------------
/frontend/src/features/recipes/recipeSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
2 | import recipeService from "./recipeService";
3 |
4 | const initialState = {
5 | recipes: [],
6 | recipe: {},
7 | isError: false,
8 | isSuccess: false,
9 | isLoading: false,
10 | message: "",
11 | };
12 |
13 | // CREATE new recipe
14 | export const createRecipe = createAsyncThunk(
15 | "recipe/create",
16 | async (recipe, thunkAPI) => {
17 | try {
18 | const token = thunkAPI.getState().auth.user.token;
19 | return await recipeService.addRecipe(recipe, token);
20 | } catch (error) {
21 | const message =
22 | (error.response &&
23 | error.response.data &&
24 | error.response.data.message) ||
25 | error.message ||
26 | error.toString();
27 |
28 | return thunkAPI.rejectWithValue(message);
29 | }
30 | }
31 | );
32 |
33 | // UPDATE existing recipe
34 | export const updateRes = createAsyncThunk(
35 | "recipe/update",
36 | async (recipe, recipeId, thunkAPI) => {
37 | try {
38 | const token = thunkAPI.getState().auth.user.token;
39 | return await recipeService.updateRecipe(recipe, recipeId, token);
40 | } catch (error) {
41 | const message =
42 | (error.response &&
43 | error.response.data &&
44 | error.response.data.message) ||
45 | error.message ||
46 | error.toString();
47 |
48 | return thunkAPI.rejectWithValue(message);
49 | }
50 | }
51 | );
52 |
53 | // GET user's recipes
54 | export const getMyRecipes = createAsyncThunk(
55 | "recipe/getAllMy",
56 | async (_, thunkAPI) => {
57 | try {
58 | const token = thunkAPI.getState().auth.user.token;
59 | return await recipeService.getMine(token);
60 | } catch (error) {
61 | const message =
62 | (error.response &&
63 | error.response.data &&
64 | error.response.data.message) ||
65 | error.message ||
66 | error.toString();
67 |
68 | return thunkAPI.rejectWithValue(message);
69 | }
70 | }
71 | );
72 |
73 | // GET all recipes
74 | export const getAllRecipes = createAsyncThunk(
75 | "recipe/getAll",
76 | async (_, thunkAPI) => {
77 | try {
78 | return await recipeService.getAllRecipes();
79 | } catch (error) {
80 | const message =
81 | (error.response &&
82 | error.response.data &&
83 | error.response.data.message) ||
84 | error.message ||
85 | error.toString();
86 |
87 | return thunkAPI.rejectWithValue(message);
88 | }
89 | }
90 | );
91 |
92 | // GET Last Three Recipes
93 | export const getLastThree = createAsyncThunk(
94 | "recipe/getLastThree",
95 | async (_, thunkAPI) => {
96 | try {
97 | return await recipeService.getLastThree();
98 | } catch (error) {
99 | const message =
100 | (error.response &&
101 | error.response.data &&
102 | error.response.data.message) ||
103 | error.message ||
104 | error.toString();
105 |
106 | return thunkAPI.rejectWithValue(message);
107 | }
108 | }
109 | );
110 |
111 | // GET Single Recipe
112 | export const getSingleRecipe = createAsyncThunk(
113 | "recipe/getRecipe",
114 | async (recipeId, thunkAPI) => {
115 | try {
116 | return await recipeService.getSingle(recipeId);
117 | } catch (error) {
118 | const message =
119 | (error.response &&
120 | error.response.data &&
121 | error.response.data.message) ||
122 | error.message ||
123 | error.toString();
124 |
125 | return thunkAPI.rejectWithValue(message);
126 | }
127 | }
128 | );
129 |
130 | // DELETE recipe
131 | export const deleteRes = createAsyncThunk(
132 | "recipe/deleteRecipe",
133 | async (recipeId, thunkAPI) => {
134 | const token = thunkAPI.getState().auth.user.token;
135 | try {
136 | return await recipeService.deleteRecipe(recipeId, token);
137 | } catch (error) {
138 | const message =
139 | (error.response &&
140 | error.response.data &&
141 | error.response.data.message) ||
142 | error.message ||
143 | error.toString();
144 |
145 | return thunkAPI.rejectWithValue(message);
146 | }
147 | }
148 | );
149 |
150 | export const recipeSlice = createSlice({
151 | name: "recipe",
152 | initialState,
153 | reducers: {
154 | reset: (state) => initialState,
155 | },
156 | extraReducers: (builder) => {
157 | builder
158 | .addCase(createRecipe.pending, (state) => {
159 | state.isLoading = true;
160 | })
161 | .addCase(createRecipe.fulfilled, (state) => {
162 | state.isLoading = false;
163 | state.isSuccess = true;
164 | })
165 | .addCase(createRecipe.rejected, (state, action) => {
166 | state.isLoading = false;
167 | state.isError = true;
168 | state.message = action.payload;
169 | })
170 | // GET my recipes
171 | .addCase(getMyRecipes.pending, (state) => {
172 | state.isLoading = true;
173 | })
174 | .addCase(getMyRecipes.fulfilled, (state, action) => {
175 | state.isLoading = false;
176 | state.isSuccess = true;
177 | state.recipes = action.payload;
178 | })
179 | .addCase(getMyRecipes.rejected, (state, action) => {
180 | state.isLoading = false;
181 | state.isError = true;
182 | state.message = action.payload;
183 | })
184 | // GET all recipes/ public
185 | .addCase(getAllRecipes.pending, (state) => {
186 | state.isLoading = true;
187 | })
188 | .addCase(getAllRecipes.fulfilled, (state, action) => {
189 | state.isLoading = false;
190 | state.isSuccess = true;
191 | state.recipes = action.payload;
192 | })
193 | .addCase(getAllRecipes.rejected, (state, action) => {
194 | state.isLoading = false;
195 | state.isError = true;
196 | state.message = action.payload;
197 | })
198 | // GET Last three recipes/ public
199 | .addCase(getLastThree.pending, (state) => {
200 | state.isLoading = true;
201 | })
202 | .addCase(getLastThree.fulfilled, (state, action) => {
203 | state.isLoading = false;
204 | state.isSuccess = true;
205 | state.recipes = action.payload;
206 | })
207 | .addCase(getLastThree.rejected, (state, action) => {
208 | state.isLoading = false;
209 | state.isError = true;
210 | state.message = action.payload;
211 | })
212 | // GET Single recipe/ public
213 | .addCase(getSingleRecipe.pending, (state) => {
214 | state.isLoading = true;
215 | })
216 | .addCase(getSingleRecipe.fulfilled, (state, action) => {
217 | state.isLoading = false;
218 | state.isSuccess = true;
219 | state.recipe = action.payload;
220 | })
221 | .addCase(getSingleRecipe.rejected, (state, action) => {
222 | state.isLoading = false;
223 | state.isError = true;
224 | state.message = action.payload;
225 | })
226 | // DELETE recipe/ private
227 | .addCase(deleteRes.pending, (state) => {
228 | state.isLoading = true;
229 | })
230 | .addCase(deleteRes.fulfilled, (state) => {
231 | state.isLoading = false;
232 | state.isSuccess = true;
233 | state.recipe = {};
234 | })
235 | .addCase(deleteRes.rejected, (state, action) => {
236 | state.isLoading = false;
237 | state.isError = true;
238 | state.message = action.payload;
239 | });
240 | },
241 | });
242 |
243 | export const { reset } = recipeSlice.actions;
244 | export default recipeSlice.reducer;
245 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;600;700&family=Lobster&family=Marck+Script&family=Montserrat:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,200;1,300&family=Nunito:ital,wght@0,300;0,400;0,500;0,700;0,900;1,300;1,400&family=Pacifico&display=swap");
2 | @import url("https://fonts.googleapis.com/css2?family=Andika:ital@0;1&family=Comfortaa:wght@300;400;500;600;700&display=swap");
3 |
4 | /*
5 | font-family: 'Andika', sans-serif;
6 |
7 | font-family: 'Comfortaa', cursive;
8 | */
9 |
10 | :root {
11 | --deep-purple: #424049;
12 | --purple: #748290;
13 | --light-purple: #a5afbb;
14 | --light-purple-transparent: #a5afbbe9;
15 | --violet: #c9b8c3;
16 | --pinky: #dc9dac;
17 | }
18 |
19 | * {
20 | box-sizing: border-box;
21 | padding: 0;
22 | margin: 0;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | }
28 |
29 | h1 {
30 | font-family: "Comfortaa", cursive;
31 | letter-spacing: 1px;
32 | margin-bottom: 1rem;
33 | }
34 |
35 | h2 {
36 | font-family: "Marck Script", cursive;
37 | font-size: 2rem;
38 | }
39 |
40 | h3 {
41 | font-family: "Comfortaa", cursive;
42 | font-size: 1.4rem;
43 | letter-spacing: 1px;
44 | font-weight: 500;
45 | }
46 |
47 | p {
48 | font-family: "Comfortaa", cursive;
49 | letter-spacing: 1px;
50 | line-height: 1.6;
51 | }
52 |
53 | a {
54 | text-decoration: none;
55 | font-family: "Comfortaa", cursive;
56 | font-weight: 900;
57 | letter-spacing: 2px;
58 | font-size: 18px;
59 | color: black;
60 | }
61 |
62 | input[type="text"],
63 | input[type="email"],
64 | input[type="password"],
65 | select {
66 | margin-top: 0.5rem;
67 | margin-bottom: 2rem;
68 | width: 100%;
69 | border: none;
70 | border-radius: 10px;
71 | height: 2.5rem;
72 | transition: 0.2s ease-in-out;
73 | background-color: rgba(105, 90, 205, 0.7);
74 | padding-left: 0.5rem;
75 | padding-right: 0.5rem;
76 | color: white;
77 | font-size: 22px;
78 | letter-spacing: 1px;
79 | font-family: "Marck Script", cursive;
80 | }
81 |
82 | input[type="text"]:focus,
83 | input[type="email"]:focus,
84 | input[type="password"]:focus,
85 | select {
86 | outline: none;
87 | border: none;
88 | border-radius: 10px;
89 | height: 3rem;
90 | padding-left: 0.5rem;
91 | padding-right: 0.5rem;
92 | caret-color: white;
93 | color: white;
94 | font-family: "Marck Script", cursive;
95 | }
96 |
97 | input[type="radio"] {
98 | height: 30px;
99 | width: 30px;
100 | vertical-align: middle;
101 | }
102 |
103 | input[type="radio"]:before {
104 | display: inline-block;
105 | width: 30px;
106 | height: 30px;
107 | border: 2px solid red;
108 | border-radius: 15px;
109 | position: relative;
110 | box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.5);
111 | margin: 0.25rem;
112 | }
113 |
114 | .btn__primary {
115 | font-family: "Nunito", sans-serif;
116 | cursor: pointer;
117 | text-transform: uppercase;
118 | letter-spacing: 2px;
119 | word-spacing: 4px;
120 | color: black;
121 | border: none;
122 | background-color: var(--violet);
123 | border-radius: 15px;
124 | padding: 12px 20px;
125 | transition: all 300ms ease-in-out;
126 | font-size: 18px;
127 | margin-top: 20px;
128 | box-shadow: 2px 4px 10px 2px rgba(0, 0, 0, 0.75);
129 | -webkit-box-shadow: 2px 4px 10px 2px rgba(0, 0, 0, 0.75);
130 | -moz-box-shadow: 2px 4px 10px 2px rgba(0, 0, 0, 0.75);
131 | }
132 |
133 | .btn__primary:hover {
134 | background-color: var(--pinky);
135 | }
136 |
137 | .regButton {
138 | display: block;
139 | cursor: pointer;
140 | font-family: "Nunito", sans-serif;
141 | letter-spacing: 2px;
142 | word-spacing: 4px;
143 | padding: 1rem;
144 | width: 100%;
145 | background-color: var(--violet);
146 | border: none;
147 | border-radius: 10px;
148 | font-size: 22px;
149 | font-weight: 700;
150 | transition: 0.3s ease-in-out;
151 | }
152 |
153 | .regButton:hover {
154 | background-color: var(--pinky);
155 | cursor: pointer;
156 | }
157 |
158 | .regButton:focus {
159 | background-color: var(--pinky);
160 | }
161 |
162 | /* INPUT */
163 |
164 | .genLabel {
165 | font-size: 20px;
166 | font-family: "Comfortaa", cursive;
167 | display: block;
168 | }
169 |
170 | ::placeholder {
171 | color: white;
172 | }
173 |
174 | /*
175 | font-family: 'Caveat', cursive;
176 |
177 | font-family: 'Lobster', cursive;
178 |
179 | font-family: 'Marck Script', cursive;
180 |
181 | font-family: 'Montserrat', sans-serif;
182 |
183 | font-family: 'Nunito', sans-serif;
184 |
185 | font-family: 'Pacifico', cursive;
186 | */
187 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import { BrowserRouter } from "react-router-dom";
4 | import { Provider } from "react-redux";
5 | import { store } from "./app/store";
6 | import "./index.css";
7 | import App from "./App";
8 |
9 | const root = ReactDOM.createRoot(document.getElementById("root"));
10 | root.render(
11 |
12 |
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-helpdesk-app",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@types/node": {
8 | "version": "18.6.3",
9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.3.tgz",
10 | "integrity": "sha512-6qKpDtoaYLM+5+AFChLhHermMQxc3TOEFIDzrZLPRGHPrLEwqFkkT5Kx3ju05g6X7uDPazz3jHbKPX0KzCjntg=="
11 | },
12 | "@types/webidl-conversions": {
13 | "version": "6.1.1",
14 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz",
15 | "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
16 | },
17 | "@types/whatwg-url": {
18 | "version": "8.2.2",
19 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
20 | "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
21 | "requires": {
22 | "@types/node": "*",
23 | "@types/webidl-conversions": "*"
24 | }
25 | },
26 | "abbrev": {
27 | "version": "1.1.1",
28 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
29 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
30 | "dev": true
31 | },
32 | "accepts": {
33 | "version": "1.3.8",
34 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
35 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
36 | "requires": {
37 | "mime-types": "~2.1.34",
38 | "negotiator": "0.6.3"
39 | }
40 | },
41 | "anymatch": {
42 | "version": "3.1.2",
43 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
44 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
45 | "dev": true,
46 | "requires": {
47 | "normalize-path": "^3.0.0",
48 | "picomatch": "^2.0.4"
49 | }
50 | },
51 | "append-field": {
52 | "version": "1.0.0",
53 | "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
54 | "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
55 | },
56 | "array-flatten": {
57 | "version": "1.1.1",
58 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
59 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
60 | },
61 | "balanced-match": {
62 | "version": "1.0.2",
63 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
64 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
65 | "dev": true
66 | },
67 | "base64-js": {
68 | "version": "1.5.1",
69 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
70 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
71 | },
72 | "bcryptjs": {
73 | "version": "2.4.3",
74 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
75 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
76 | },
77 | "binary-extensions": {
78 | "version": "2.2.0",
79 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
80 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
81 | "dev": true
82 | },
83 | "body-parser": {
84 | "version": "1.20.0",
85 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
86 | "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
87 | "requires": {
88 | "bytes": "3.1.2",
89 | "content-type": "~1.0.4",
90 | "debug": "2.6.9",
91 | "depd": "2.0.0",
92 | "destroy": "1.2.0",
93 | "http-errors": "2.0.0",
94 | "iconv-lite": "0.4.24",
95 | "on-finished": "2.4.1",
96 | "qs": "6.10.3",
97 | "raw-body": "2.5.1",
98 | "type-is": "~1.6.18",
99 | "unpipe": "1.0.0"
100 | }
101 | },
102 | "brace-expansion": {
103 | "version": "1.1.11",
104 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
105 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
106 | "dev": true,
107 | "requires": {
108 | "balanced-match": "^1.0.0",
109 | "concat-map": "0.0.1"
110 | }
111 | },
112 | "braces": {
113 | "version": "3.0.2",
114 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
115 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
116 | "dev": true,
117 | "requires": {
118 | "fill-range": "^7.0.1"
119 | }
120 | },
121 | "bson": {
122 | "version": "4.6.5",
123 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.5.tgz",
124 | "integrity": "sha512-uqrgcjyOaZsHfz7ea8zLRCLe1u+QGUSzMZmvXqO24CDW7DWoW1qiN9folSwa7hSneTSgM2ykDIzF5kcQQ8cwNw==",
125 | "requires": {
126 | "buffer": "^5.6.0"
127 | }
128 | },
129 | "buffer": {
130 | "version": "5.7.1",
131 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
132 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
133 | "requires": {
134 | "base64-js": "^1.3.1",
135 | "ieee754": "^1.1.13"
136 | }
137 | },
138 | "buffer-equal-constant-time": {
139 | "version": "1.0.1",
140 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
141 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
142 | },
143 | "buffer-from": {
144 | "version": "1.1.2",
145 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
146 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
147 | },
148 | "busboy": {
149 | "version": "1.6.0",
150 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
151 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
152 | "requires": {
153 | "streamsearch": "^1.1.0"
154 | }
155 | },
156 | "bytes": {
157 | "version": "3.1.2",
158 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
159 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
160 | },
161 | "call-bind": {
162 | "version": "1.0.2",
163 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
164 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
165 | "requires": {
166 | "function-bind": "^1.1.1",
167 | "get-intrinsic": "^1.0.2"
168 | }
169 | },
170 | "chokidar": {
171 | "version": "3.5.3",
172 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
173 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
174 | "dev": true,
175 | "requires": {
176 | "anymatch": "~3.1.2",
177 | "braces": "~3.0.2",
178 | "fsevents": "~2.3.2",
179 | "glob-parent": "~5.1.2",
180 | "is-binary-path": "~2.1.0",
181 | "is-glob": "~4.0.1",
182 | "normalize-path": "~3.0.0",
183 | "readdirp": "~3.6.0"
184 | }
185 | },
186 | "colors": {
187 | "version": "1.4.0",
188 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
189 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
190 | },
191 | "concat-map": {
192 | "version": "0.0.1",
193 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
194 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
195 | "dev": true
196 | },
197 | "concat-stream": {
198 | "version": "1.6.2",
199 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
200 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
201 | "requires": {
202 | "buffer-from": "^1.0.0",
203 | "inherits": "^2.0.3",
204 | "readable-stream": "^2.2.2",
205 | "typedarray": "^0.0.6"
206 | }
207 | },
208 | "content-disposition": {
209 | "version": "0.5.4",
210 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
211 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
212 | "requires": {
213 | "safe-buffer": "5.2.1"
214 | }
215 | },
216 | "content-type": {
217 | "version": "1.0.4",
218 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
219 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
220 | },
221 | "cookie": {
222 | "version": "0.5.0",
223 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
224 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
225 | },
226 | "cookie-signature": {
227 | "version": "1.0.6",
228 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
229 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
230 | },
231 | "core-util-is": {
232 | "version": "1.0.3",
233 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
234 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
235 | },
236 | "debug": {
237 | "version": "2.6.9",
238 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
239 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
240 | "requires": {
241 | "ms": "2.0.0"
242 | }
243 | },
244 | "denque": {
245 | "version": "2.1.0",
246 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
247 | "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="
248 | },
249 | "depd": {
250 | "version": "2.0.0",
251 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
252 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
253 | },
254 | "destroy": {
255 | "version": "1.2.0",
256 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
257 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
258 | },
259 | "dotenv": {
260 | "version": "16.0.1",
261 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
262 | "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ=="
263 | },
264 | "ecdsa-sig-formatter": {
265 | "version": "1.0.11",
266 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
267 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
268 | "requires": {
269 | "safe-buffer": "^5.0.1"
270 | }
271 | },
272 | "ee-first": {
273 | "version": "1.1.1",
274 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
275 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
276 | },
277 | "encodeurl": {
278 | "version": "1.0.2",
279 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
280 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
281 | },
282 | "escape-html": {
283 | "version": "1.0.3",
284 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
285 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
286 | },
287 | "etag": {
288 | "version": "1.8.1",
289 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
290 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
291 | },
292 | "express": {
293 | "version": "4.18.1",
294 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
295 | "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
296 | "requires": {
297 | "accepts": "~1.3.8",
298 | "array-flatten": "1.1.1",
299 | "body-parser": "1.20.0",
300 | "content-disposition": "0.5.4",
301 | "content-type": "~1.0.4",
302 | "cookie": "0.5.0",
303 | "cookie-signature": "1.0.6",
304 | "debug": "2.6.9",
305 | "depd": "2.0.0",
306 | "encodeurl": "~1.0.2",
307 | "escape-html": "~1.0.3",
308 | "etag": "~1.8.1",
309 | "finalhandler": "1.2.0",
310 | "fresh": "0.5.2",
311 | "http-errors": "2.0.0",
312 | "merge-descriptors": "1.0.1",
313 | "methods": "~1.1.2",
314 | "on-finished": "2.4.1",
315 | "parseurl": "~1.3.3",
316 | "path-to-regexp": "0.1.7",
317 | "proxy-addr": "~2.0.7",
318 | "qs": "6.10.3",
319 | "range-parser": "~1.2.1",
320 | "safe-buffer": "5.2.1",
321 | "send": "0.18.0",
322 | "serve-static": "1.15.0",
323 | "setprototypeof": "1.2.0",
324 | "statuses": "2.0.1",
325 | "type-is": "~1.6.18",
326 | "utils-merge": "1.0.1",
327 | "vary": "~1.1.2"
328 | }
329 | },
330 | "express-async-handler": {
331 | "version": "1.2.0",
332 | "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz",
333 | "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w=="
334 | },
335 | "fill-range": {
336 | "version": "7.0.1",
337 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
338 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
339 | "dev": true,
340 | "requires": {
341 | "to-regex-range": "^5.0.1"
342 | }
343 | },
344 | "finalhandler": {
345 | "version": "1.2.0",
346 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
347 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
348 | "requires": {
349 | "debug": "2.6.9",
350 | "encodeurl": "~1.0.2",
351 | "escape-html": "~1.0.3",
352 | "on-finished": "2.4.1",
353 | "parseurl": "~1.3.3",
354 | "statuses": "2.0.1",
355 | "unpipe": "~1.0.0"
356 | }
357 | },
358 | "forwarded": {
359 | "version": "0.2.0",
360 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
361 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
362 | },
363 | "fresh": {
364 | "version": "0.5.2",
365 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
366 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
367 | },
368 | "fsevents": {
369 | "version": "2.3.2",
370 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
371 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
372 | "dev": true,
373 | "optional": true
374 | },
375 | "function-bind": {
376 | "version": "1.1.1",
377 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
378 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
379 | },
380 | "get-intrinsic": {
381 | "version": "1.1.2",
382 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
383 | "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
384 | "requires": {
385 | "function-bind": "^1.1.1",
386 | "has": "^1.0.3",
387 | "has-symbols": "^1.0.3"
388 | }
389 | },
390 | "glob-parent": {
391 | "version": "5.1.2",
392 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
393 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
394 | "dev": true,
395 | "requires": {
396 | "is-glob": "^4.0.1"
397 | }
398 | },
399 | "has": {
400 | "version": "1.0.3",
401 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
402 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
403 | "requires": {
404 | "function-bind": "^1.1.1"
405 | }
406 | },
407 | "has-flag": {
408 | "version": "3.0.0",
409 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
410 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
411 | "dev": true
412 | },
413 | "has-symbols": {
414 | "version": "1.0.3",
415 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
416 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
417 | },
418 | "http-errors": {
419 | "version": "2.0.0",
420 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
421 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
422 | "requires": {
423 | "depd": "2.0.0",
424 | "inherits": "2.0.4",
425 | "setprototypeof": "1.2.0",
426 | "statuses": "2.0.1",
427 | "toidentifier": "1.0.1"
428 | }
429 | },
430 | "iconv-lite": {
431 | "version": "0.4.24",
432 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
433 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
434 | "requires": {
435 | "safer-buffer": ">= 2.1.2 < 3"
436 | }
437 | },
438 | "ieee754": {
439 | "version": "1.2.1",
440 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
441 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
442 | },
443 | "ignore-by-default": {
444 | "version": "1.0.1",
445 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
446 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
447 | "dev": true
448 | },
449 | "inherits": {
450 | "version": "2.0.4",
451 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
452 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
453 | },
454 | "ip": {
455 | "version": "2.0.0",
456 | "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
457 | "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
458 | },
459 | "ipaddr.js": {
460 | "version": "1.9.1",
461 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
462 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
463 | },
464 | "is-binary-path": {
465 | "version": "2.1.0",
466 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
467 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
468 | "dev": true,
469 | "requires": {
470 | "binary-extensions": "^2.0.0"
471 | }
472 | },
473 | "is-extglob": {
474 | "version": "2.1.1",
475 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
476 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
477 | "dev": true
478 | },
479 | "is-glob": {
480 | "version": "4.0.3",
481 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
482 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
483 | "dev": true,
484 | "requires": {
485 | "is-extglob": "^2.1.1"
486 | }
487 | },
488 | "is-number": {
489 | "version": "7.0.0",
490 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
491 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
492 | "dev": true
493 | },
494 | "isarray": {
495 | "version": "1.0.0",
496 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
497 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
498 | },
499 | "jsonwebtoken": {
500 | "version": "8.5.1",
501 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
502 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
503 | "requires": {
504 | "jws": "^3.2.2",
505 | "lodash.includes": "^4.3.0",
506 | "lodash.isboolean": "^3.0.3",
507 | "lodash.isinteger": "^4.0.4",
508 | "lodash.isnumber": "^3.0.3",
509 | "lodash.isplainobject": "^4.0.6",
510 | "lodash.isstring": "^4.0.1",
511 | "lodash.once": "^4.0.0",
512 | "ms": "^2.1.1",
513 | "semver": "^5.6.0"
514 | },
515 | "dependencies": {
516 | "ms": {
517 | "version": "2.1.3",
518 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
519 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
520 | }
521 | }
522 | },
523 | "jwa": {
524 | "version": "1.4.1",
525 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
526 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
527 | "requires": {
528 | "buffer-equal-constant-time": "1.0.1",
529 | "ecdsa-sig-formatter": "1.0.11",
530 | "safe-buffer": "^5.0.1"
531 | }
532 | },
533 | "jws": {
534 | "version": "3.2.2",
535 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
536 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
537 | "requires": {
538 | "jwa": "^1.4.1",
539 | "safe-buffer": "^5.0.1"
540 | }
541 | },
542 | "kareem": {
543 | "version": "2.4.1",
544 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
545 | "integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
546 | },
547 | "lodash.includes": {
548 | "version": "4.3.0",
549 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
550 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
551 | },
552 | "lodash.isboolean": {
553 | "version": "3.0.3",
554 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
555 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
556 | },
557 | "lodash.isinteger": {
558 | "version": "4.0.4",
559 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
560 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
561 | },
562 | "lodash.isnumber": {
563 | "version": "3.0.3",
564 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
565 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
566 | },
567 | "lodash.isplainobject": {
568 | "version": "4.0.6",
569 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
570 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
571 | },
572 | "lodash.isstring": {
573 | "version": "4.0.1",
574 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
575 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
576 | },
577 | "lodash.once": {
578 | "version": "4.1.1",
579 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
580 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
581 | },
582 | "media-typer": {
583 | "version": "0.3.0",
584 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
585 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
586 | },
587 | "memory-pager": {
588 | "version": "1.5.0",
589 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
590 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
591 | "optional": true
592 | },
593 | "merge-descriptors": {
594 | "version": "1.0.1",
595 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
596 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
597 | },
598 | "methods": {
599 | "version": "1.1.2",
600 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
601 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
602 | },
603 | "mime": {
604 | "version": "1.6.0",
605 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
606 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
607 | },
608 | "mime-db": {
609 | "version": "1.52.0",
610 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
611 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
612 | },
613 | "mime-types": {
614 | "version": "2.1.35",
615 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
616 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
617 | "requires": {
618 | "mime-db": "1.52.0"
619 | }
620 | },
621 | "minimatch": {
622 | "version": "3.1.2",
623 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
624 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
625 | "dev": true,
626 | "requires": {
627 | "brace-expansion": "^1.1.7"
628 | }
629 | },
630 | "minimist": {
631 | "version": "1.2.6",
632 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
633 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
634 | },
635 | "mkdirp": {
636 | "version": "0.5.6",
637 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
638 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
639 | "requires": {
640 | "minimist": "^1.2.6"
641 | }
642 | },
643 | "mongodb": {
644 | "version": "4.8.1",
645 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.8.1.tgz",
646 | "integrity": "sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w==",
647 | "requires": {
648 | "bson": "^4.6.5",
649 | "denque": "^2.0.1",
650 | "mongodb-connection-string-url": "^2.5.2",
651 | "saslprep": "^1.0.3",
652 | "socks": "^2.6.2"
653 | }
654 | },
655 | "mongodb-connection-string-url": {
656 | "version": "2.5.3",
657 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz",
658 | "integrity": "sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==",
659 | "requires": {
660 | "@types/whatwg-url": "^8.2.1",
661 | "whatwg-url": "^11.0.0"
662 | }
663 | },
664 | "mongoose": {
665 | "version": "6.5.0",
666 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.0.tgz",
667 | "integrity": "sha512-swOX8ZEbmCeJaEs29B1j67StBIhuOccNNkipbVhsnLYYCDpNE7heM9W54MFGwN5es9tGGoxINHSzOhJ9kTOZGg==",
668 | "requires": {
669 | "bson": "^4.6.5",
670 | "kareem": "2.4.1",
671 | "mongodb": "4.8.1",
672 | "mpath": "0.9.0",
673 | "mquery": "4.0.3",
674 | "ms": "2.1.3",
675 | "sift": "16.0.0"
676 | },
677 | "dependencies": {
678 | "ms": {
679 | "version": "2.1.3",
680 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
681 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
682 | }
683 | }
684 | },
685 | "mpath": {
686 | "version": "0.9.0",
687 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
688 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="
689 | },
690 | "mquery": {
691 | "version": "4.0.3",
692 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
693 | "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
694 | "requires": {
695 | "debug": "4.x"
696 | },
697 | "dependencies": {
698 | "debug": {
699 | "version": "4.3.4",
700 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
701 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
702 | "requires": {
703 | "ms": "2.1.2"
704 | }
705 | },
706 | "ms": {
707 | "version": "2.1.2",
708 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
709 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
710 | }
711 | }
712 | },
713 | "ms": {
714 | "version": "2.0.0",
715 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
716 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
717 | },
718 | "multer": {
719 | "version": "1.4.5-lts.1",
720 | "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
721 | "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
722 | "requires": {
723 | "append-field": "^1.0.0",
724 | "busboy": "^1.0.0",
725 | "concat-stream": "^1.5.2",
726 | "mkdirp": "^0.5.4",
727 | "object-assign": "^4.1.1",
728 | "type-is": "^1.6.4",
729 | "xtend": "^4.0.0"
730 | }
731 | },
732 | "negotiator": {
733 | "version": "0.6.3",
734 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
735 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
736 | },
737 | "nodemon": {
738 | "version": "2.0.19",
739 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz",
740 | "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==",
741 | "dev": true,
742 | "requires": {
743 | "chokidar": "^3.5.2",
744 | "debug": "^3.2.7",
745 | "ignore-by-default": "^1.0.1",
746 | "minimatch": "^3.0.4",
747 | "pstree.remy": "^1.1.8",
748 | "semver": "^5.7.1",
749 | "simple-update-notifier": "^1.0.7",
750 | "supports-color": "^5.5.0",
751 | "touch": "^3.1.0",
752 | "undefsafe": "^2.0.5"
753 | },
754 | "dependencies": {
755 | "debug": {
756 | "version": "3.2.7",
757 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
758 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
759 | "dev": true,
760 | "requires": {
761 | "ms": "^2.1.1"
762 | }
763 | },
764 | "ms": {
765 | "version": "2.1.3",
766 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
767 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
768 | "dev": true
769 | }
770 | }
771 | },
772 | "nopt": {
773 | "version": "1.0.10",
774 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
775 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
776 | "dev": true,
777 | "requires": {
778 | "abbrev": "1"
779 | }
780 | },
781 | "normalize-path": {
782 | "version": "3.0.0",
783 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
784 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
785 | "dev": true
786 | },
787 | "object-assign": {
788 | "version": "4.1.1",
789 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
790 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
791 | },
792 | "object-inspect": {
793 | "version": "1.12.2",
794 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
795 | "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
796 | },
797 | "on-finished": {
798 | "version": "2.4.1",
799 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
800 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
801 | "requires": {
802 | "ee-first": "1.1.1"
803 | }
804 | },
805 | "parseurl": {
806 | "version": "1.3.3",
807 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
808 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
809 | },
810 | "path-to-regexp": {
811 | "version": "0.1.7",
812 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
813 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
814 | },
815 | "picomatch": {
816 | "version": "2.3.1",
817 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
818 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
819 | "dev": true
820 | },
821 | "process-nextick-args": {
822 | "version": "2.0.1",
823 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
824 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
825 | },
826 | "proxy-addr": {
827 | "version": "2.0.7",
828 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
829 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
830 | "requires": {
831 | "forwarded": "0.2.0",
832 | "ipaddr.js": "1.9.1"
833 | }
834 | },
835 | "pstree.remy": {
836 | "version": "1.1.8",
837 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
838 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
839 | "dev": true
840 | },
841 | "punycode": {
842 | "version": "2.1.1",
843 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
844 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
845 | },
846 | "qs": {
847 | "version": "6.10.3",
848 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
849 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
850 | "requires": {
851 | "side-channel": "^1.0.4"
852 | }
853 | },
854 | "range-parser": {
855 | "version": "1.2.1",
856 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
857 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
858 | },
859 | "raw-body": {
860 | "version": "2.5.1",
861 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
862 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
863 | "requires": {
864 | "bytes": "3.1.2",
865 | "http-errors": "2.0.0",
866 | "iconv-lite": "0.4.24",
867 | "unpipe": "1.0.0"
868 | }
869 | },
870 | "readable-stream": {
871 | "version": "2.3.7",
872 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
873 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
874 | "requires": {
875 | "core-util-is": "~1.0.0",
876 | "inherits": "~2.0.3",
877 | "isarray": "~1.0.0",
878 | "process-nextick-args": "~2.0.0",
879 | "safe-buffer": "~5.1.1",
880 | "string_decoder": "~1.1.1",
881 | "util-deprecate": "~1.0.1"
882 | },
883 | "dependencies": {
884 | "safe-buffer": {
885 | "version": "5.1.2",
886 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
887 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
888 | }
889 | }
890 | },
891 | "readdirp": {
892 | "version": "3.6.0",
893 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
894 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
895 | "dev": true,
896 | "requires": {
897 | "picomatch": "^2.2.1"
898 | }
899 | },
900 | "safe-buffer": {
901 | "version": "5.2.1",
902 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
903 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
904 | },
905 | "safer-buffer": {
906 | "version": "2.1.2",
907 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
908 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
909 | },
910 | "saslprep": {
911 | "version": "1.0.3",
912 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
913 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
914 | "optional": true,
915 | "requires": {
916 | "sparse-bitfield": "^3.0.3"
917 | }
918 | },
919 | "semver": {
920 | "version": "5.7.1",
921 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
922 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
923 | },
924 | "send": {
925 | "version": "0.18.0",
926 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
927 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
928 | "requires": {
929 | "debug": "2.6.9",
930 | "depd": "2.0.0",
931 | "destroy": "1.2.0",
932 | "encodeurl": "~1.0.2",
933 | "escape-html": "~1.0.3",
934 | "etag": "~1.8.1",
935 | "fresh": "0.5.2",
936 | "http-errors": "2.0.0",
937 | "mime": "1.6.0",
938 | "ms": "2.1.3",
939 | "on-finished": "2.4.1",
940 | "range-parser": "~1.2.1",
941 | "statuses": "2.0.1"
942 | },
943 | "dependencies": {
944 | "ms": {
945 | "version": "2.1.3",
946 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
947 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
948 | }
949 | }
950 | },
951 | "serve-static": {
952 | "version": "1.15.0",
953 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
954 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
955 | "requires": {
956 | "encodeurl": "~1.0.2",
957 | "escape-html": "~1.0.3",
958 | "parseurl": "~1.3.3",
959 | "send": "0.18.0"
960 | }
961 | },
962 | "setprototypeof": {
963 | "version": "1.2.0",
964 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
965 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
966 | },
967 | "side-channel": {
968 | "version": "1.0.4",
969 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
970 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
971 | "requires": {
972 | "call-bind": "^1.0.0",
973 | "get-intrinsic": "^1.0.2",
974 | "object-inspect": "^1.9.0"
975 | }
976 | },
977 | "sift": {
978 | "version": "16.0.0",
979 | "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
980 | "integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
981 | },
982 | "simple-update-notifier": {
983 | "version": "1.0.7",
984 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz",
985 | "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==",
986 | "dev": true,
987 | "requires": {
988 | "semver": "~7.0.0"
989 | },
990 | "dependencies": {
991 | "semver": {
992 | "version": "7.0.0",
993 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
994 | "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
995 | "dev": true
996 | }
997 | }
998 | },
999 | "smart-buffer": {
1000 | "version": "4.2.0",
1001 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
1002 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
1003 | },
1004 | "socks": {
1005 | "version": "2.7.0",
1006 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz",
1007 | "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==",
1008 | "requires": {
1009 | "ip": "^2.0.0",
1010 | "smart-buffer": "^4.2.0"
1011 | }
1012 | },
1013 | "sparse-bitfield": {
1014 | "version": "3.0.3",
1015 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1016 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
1017 | "optional": true,
1018 | "requires": {
1019 | "memory-pager": "^1.0.2"
1020 | }
1021 | },
1022 | "statuses": {
1023 | "version": "2.0.1",
1024 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1025 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
1026 | },
1027 | "streamsearch": {
1028 | "version": "1.1.0",
1029 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
1030 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
1031 | },
1032 | "string_decoder": {
1033 | "version": "1.1.1",
1034 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
1035 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
1036 | "requires": {
1037 | "safe-buffer": "~5.1.0"
1038 | },
1039 | "dependencies": {
1040 | "safe-buffer": {
1041 | "version": "5.1.2",
1042 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1043 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
1044 | }
1045 | }
1046 | },
1047 | "supports-color": {
1048 | "version": "5.5.0",
1049 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1050 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1051 | "dev": true,
1052 | "requires": {
1053 | "has-flag": "^3.0.0"
1054 | }
1055 | },
1056 | "to-regex-range": {
1057 | "version": "5.0.1",
1058 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1059 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1060 | "dev": true,
1061 | "requires": {
1062 | "is-number": "^7.0.0"
1063 | }
1064 | },
1065 | "toidentifier": {
1066 | "version": "1.0.1",
1067 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1068 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
1069 | },
1070 | "touch": {
1071 | "version": "3.1.0",
1072 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
1073 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
1074 | "dev": true,
1075 | "requires": {
1076 | "nopt": "~1.0.10"
1077 | }
1078 | },
1079 | "tr46": {
1080 | "version": "3.0.0",
1081 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
1082 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
1083 | "requires": {
1084 | "punycode": "^2.1.1"
1085 | }
1086 | },
1087 | "type-is": {
1088 | "version": "1.6.18",
1089 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1090 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1091 | "requires": {
1092 | "media-typer": "0.3.0",
1093 | "mime-types": "~2.1.24"
1094 | }
1095 | },
1096 | "typedarray": {
1097 | "version": "0.0.6",
1098 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
1099 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
1100 | },
1101 | "undefsafe": {
1102 | "version": "2.0.5",
1103 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1104 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
1105 | "dev": true
1106 | },
1107 | "unpipe": {
1108 | "version": "1.0.0",
1109 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1110 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
1111 | },
1112 | "util-deprecate": {
1113 | "version": "1.0.2",
1114 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1115 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
1116 | },
1117 | "utils-merge": {
1118 | "version": "1.0.1",
1119 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1120 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
1121 | },
1122 | "vary": {
1123 | "version": "1.1.2",
1124 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1125 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
1126 | },
1127 | "webidl-conversions": {
1128 | "version": "7.0.0",
1129 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1130 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
1131 | },
1132 | "whatwg-url": {
1133 | "version": "11.0.0",
1134 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
1135 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
1136 | "requires": {
1137 | "tr46": "^3.0.0",
1138 | "webidl-conversions": "^7.0.0"
1139 | }
1140 | },
1141 | "xtend": {
1142 | "version": "4.0.2",
1143 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
1144 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
1145 | }
1146 | }
1147 | }
1148 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-helpdesk-app",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "npm install && cd frontend && npm install && npm run build",
8 | "start": "node backend/server.js",
9 | "server": "nodemon backend/server.js"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "bcryptjs": "^2.4.3",
16 | "colors": "^1.4.0",
17 | "dotenv": "^16.0.1",
18 | "express": "^4.18.1",
19 | "express-async-handler": "^1.2.0",
20 | "jsonwebtoken": "^8.5.1",
21 | "mongoose": "^6.5.0",
22 | "multer": "^1.4.5-lts.1"
23 | },
24 | "devDependencies": {
25 | "nodemon": "^2.0.19"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------