diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..1588f65
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,24 @@
+---
+name: ๐ Bug Report
+about: ๋ฒ๊ทธ๋ฅผ ๋ณด๊ณ ํฉ๋๋ค
+title: "[Bug] "
+labels: bug
+assignees: ''
+---
+
+## ๐ ๋ฒ๊ทธ ์ค๋ช
+- ๋ฒ๊ทธ์ ๋ํด ๊ฐ๋จํ ์ค๋ช
ํด์ฃผ์ธ์.
+
+## โ
์ฌํ ๋ฐฉ๋ฒ
+- ๋ฒ๊ทธ๋ฅผ ์ฌํํ๋ ค๋ฉด ๋ค์ ๋จ๊ณ๋ฅผ ๋ฐ๋ฅด์ธ์:
+ 1. [์: ํ์ด์ง ์ด๋ ๊ฒฝ๋ก]
+ 2. [์: ํน์ ๋ฒํผ ํด๋ฆญ]
+ 3. [์: ๊ธฐ๋๋๋ ๊ฒฐ๊ณผ]
+
+## ๐ฅ ํ๊ฒฝ ์ ๋ณด
+- OS: [Windows, macOS, Linux ๋ฑ]
+- Browser: [Chrome, Firefox ๋ฑ]
+- Version: [์ฑ/๋ธ๋ผ์ฐ์ ๋ฒ์ ]
+
+## ๐ธ ์คํฌ๋ฆฐ์ท
+- ๋ฒ๊ทธ๋ฅผ ๋ณด์ฌ์ฃผ๋ ์คํฌ๋ฆฐ์ท์ ์ถ๊ฐํด์ฃผ์ธ์ (ํ์ํ ๊ฒฝ์ฐ).
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..9880a67
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,16 @@
+---
+name: โจ Feature Request
+about: ์๋ก์ด ๊ธฐ๋ฅ์ ์์ฒญํฉ๋๋ค
+title: "[Feature] "
+labels: enhancement
+assignees: ''
+---
+
+## ๐ ๊ธฐ๋ฅ ์ค๋ช
+- ์ถ๊ฐํ๊ณ ์ถ์ ๊ธฐ๋ฅ์ด๋ ๋ณ๊ฒฝ ์ฌํญ์ ๊ฐ๋ตํ ์ค๋ช
ํด์ฃผ์ธ์.
+
+## ๐ค ์ด์
+- ์ด ๊ธฐ๋ฅ์ด ์ ํ์ํ์ง, ์ด๋ค ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋์ง ์ค๋ช
ํด์ฃผ์ธ์.
+
+## ๐ ์ถ๊ฐ ์ ๋ณด
+- ๊ธฐ๋ฅ๊ณผ ๊ด๋ จ๋ ์ฐธ๊ณ ์๋ฃ๋ ์ถ๊ฐ ์ ๋ณด๋ฅผ ํฌํจํด์ฃผ์ธ์.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..3d75a26
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,34 @@
+## ๐ ์์ฝ
+- ์ด Pull Request์ ๋ชฉ์ ๊ณผ ์ฃผ์ ๋ณ๊ฒฝ ์ฌํญ์ ๊ฐ๋ตํ ์ค๋ช
ํด์ฃผ์ธ์.
+
+## ๐ ๋ณ๊ฒฝ ์ฌํญ
+- ์ด๋ฒ Pull Request์์ ์์
ํ ์ฃผ์ ๋ณ๊ฒฝ ์ฌํญ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
+ - [x] ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ ๋๋ ๋ฒ๊ทธ ์์
+ - [x] ์ฝ๋ ๋ฆฌํฉํ ๋ง ๋๋ ์ต์ ํ
+ - [x] ๋ฌธ์ ์
๋ฐ์ดํธ
+
+## ๐ ๊ด๋ จ ์ด์
+- ์ด PR๋ก ํด๊ฒฐ๋๋ ์ด์: #[์ด์ ๋ฒํธ]
+- ๊ด๋ จ๋ ์ด์: #[์ด์ ๋ฒํธ]
+
+## ๐ธ ์คํฌ๋ฆฐ์ท ๋๋ GIF (ํด๋น๋๋ ๊ฒฝ์ฐ)
+- UI ๋ณ๊ฒฝ์ด ์๋ ๊ฒฝ์ฐ ๋ณ๊ฒฝ ์ฌํญ์ ๋ณด์ฌ์ฃผ๋ ์คํฌ๋ฆฐ์ท์ด๋ GIF๋ฅผ ์ถ๊ฐํด์ฃผ์ธ์.
+
+## โ
์ฒดํฌ๋ฆฌ์คํธ
+- [ ] ์ฝ๋๊ฐ ์ ์์ ์ผ๋ก ๋์ํ๋์ง ํ
์คํธํ์ต๋๋ค.
+- [ ] ๊ด๋ จ ๋ฌธ์๋ฅผ ์์ฑํ๊ฑฐ๋ ์
๋ฐ์ดํธํ์ต๋๋ค. (ํด๋น๋๋ ๊ฒฝ์ฐ)
+- [ ] ๋ณ๊ฒฝ ์ฌํญ์ ๋ฐ์ํ ํ
์คํธ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
+- [ ] ๋ชจ๋ ํ
์คํธ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ํต๊ณผํ์ต๋๋ค.
+
+## ๐ก ํ
์คํธ ๋ฐฉ๋ฒ
+- ๋ณ๊ฒฝ ์ฌํญ์ ํ์ธํ๊ธฐ ์ํ ํ
์คํธ ๋ฐฉ๋ฒ์ ๋จ๊ณ๋ณ๋ก ์์ฑํด์ฃผ์ธ์:
+ 1. ๋ธ๋์น๋ฅผ ๋ก์ปฌ๋ก ๊ฐ์ ธ์ต๋๋ค.
+ 2. ์๋ ๋ช
๋ น์ด๋ฅผ ์ฌ์ฉํด ์ ํ๋ฆฌ์ผ์ด์
๋๋ ํ
์คํธ๋ฅผ ์คํํฉ๋๋ค:
+ ```
+ # ์์ ๋ช
๋ น์ด
+ ./run_tests.sh
+ ```
+ 3. ์์ ๋์์ด๋ ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํฉ๋๋ค.
+
+## ๐ ์ถ๊ฐ ์ฐธ๊ณ ์ฌํญ
+- ์ด PR๊ณผ ๊ด๋ จํด ๋ฆฌ๋ทฐ์ด๊ฐ ์์์ผ ํ ์ถ๊ฐ ๋ด์ฉ์ด ์๋ค๋ฉด ์์ฑํด์ฃผ์ธ์.
diff --git a/src/App.jsx b/src/App.jsx
index 90d4bac..0bfc956 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,21 +1,27 @@
import { BrowserRouter, Routes, Route } from "react-router-dom"
import Layout from "./pages/layouts/Layout"
import Ingredient from "./pages/ingredient/Ingregdient"
+import Recipe from "./pages/recipe/Recipe"
+import {AddRecipe} from "./pages/recipe/AddRecipe"
import UsersIngredientItem from "./components/ingredient/UsersIngredientItem"
function App() {
- return (
-
-
- }>
-
- }/>
- {/* } /> */}
-
-
-
-
- )
-}
+ return (
+
+
+ }>
+
+ }/>
+ {/* } /> */}
+
+
+ } />
+ } />
+
+
-export default App
+
+
+ )
+}
+export default App
\ No newline at end of file
diff --git a/src/assets/image/noimage.jpg b/src/assets/image/noimage.jpg
new file mode 100644
index 0000000..110d770
Binary files /dev/null and b/src/assets/image/noimage.jpg differ
diff --git a/src/components/recipe/default.jsx b/src/components/recipe/default.jsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/pages/recipe/Recipe.jsx b/src/pages/recipe/Recipe.jsx
new file mode 100644
index 0000000..b965de2
--- /dev/null
+++ b/src/pages/recipe/Recipe.jsx
@@ -0,0 +1,40 @@
+import { useEffect, useState,useCallback } from "react"
+import { getRecipeList } from "../../sources/api/recipeAPI.jsx";
+import {Route, useNavigate} from "react-router-dom";
+import { AddRecipe} from "./addRecipe.jsx";
+
+function Recipe() {
+ const [recipeList, setRecipeList] = useState([])
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const fetchRecipes = async () => {
+ try {
+ const data = await getRecipeList();
+ setRecipeList(data); // ์ํ ์
๋ฐ์ดํธ
+ console.log(recipeList); // ๊ฐ์ ธ์จ ๋ฐ์ดํฐ ํ์ธ
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ fetchRecipes(); // ๋น๋๊ธฐ ์์
ํธ์ถ
+ }, []);
+
+ const handleAdd = useCallback(() => {
+ navigate("/recipe/create");
+ },[navigate])
+
+ return (
+ <>
+
๋ ์ํผ ํ์ด์ง
+
+ >
+ )
+}
+
+export default Recipe
\ No newline at end of file
diff --git a/src/pages/recipe/addRecipe.jsx b/src/pages/recipe/addRecipe.jsx
new file mode 100644
index 0000000..f04c1e3
--- /dev/null
+++ b/src/pages/recipe/addRecipe.jsx
@@ -0,0 +1,317 @@
+
+import { useState } from 'react';
+import { createRecipe } from "../../sources/api/recipeAPI.jsx";
+
+const initialState = {
+ recipeName: '',
+ recipeCookingTime: 0,
+ recipeDifficulty: 0,
+ recipeContent: '',
+ recipeSteps: [],
+ recipeCategoryPk: 0,
+ userPk: 0,
+};
+
+export const AddRecipe = () => {
+
+ const [request, setRequest] = useState({ ...initialState });
+
+ const [recipeSources, setRecipeSources] = useState([]);
+
+ const [recipeStepSources, setRecipeStepSources] = useState([]);
+
+ const [result, setResult] = useState(null);
+
+ const defaultUrl = 'https://picsum.photos/200/300';
+ const handleClickAdd = () => {
+
+ const formData = new FormData();
+
+ // request ๊ธฐ๋ณธ ๊ธ์ ๊ทธ๋ฅ ์ถ๊ฐ
+ formData.append("request",new Blob([JSON.stringify(request)],{type:"application/json"}));
+
+ // recipeSources ํ์ผ ๋ฐฐ์ด ์ถ๊ฐ
+ recipeSources.forEach(file => {
+ formData.append(`recipeSources`, file); // ๊ฐ์ ์ด๋ฆ์ผ๋ก ์๋ฒ์ ์ ๋ฌ
+ });
+
+ // recipeStepSources ์ถ๊ฐ
+ recipeStepSources.forEach((files, stepIndex) => {
+ if(files) {
+ files.forEach(file => {
+ formData.append(`recipeStepSources`, file);
+ })
+ }
+ });
+
+ createRecipe(formData)
+ .then(res => {
+ console.log(res);
+ setResult(res);
+
+ // ์ฌ์ฉ ํ ์ด๊ธฐํ
+ setRequest({ ...initialState });
+ setRecipeSources([]);
+ setRecipeStepSources([]);
+ })
+ .catch(err => {
+ console.log(err);
+ });
+ };
+
+ // ํ์ผ ์
๋ก๋ ์ฒ๋ฆฌ
+ const handleRecipeSourcesChange = (e) => {
+ setRecipeSources(Array.from(e.target.files)); // ์ฌ๋ฌ ํ์ผ์ ๋ฐฐ์ด๋ก ์ถ๊ฐ
+ };
+
+
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setRequest(prevState => ({
+ ...prevState,
+ [name]: value,
+ }));
+ };
+
+ const addStep = (index) => {
+ setRequest((prevState) => {
+ const updatedSteps = [...prevState.recipeSteps];
+ const newStep = {
+ recipeStepOrder: index + 2,
+ recipeStepContent: '',
+ };
+
+ updatedSteps.splice(index + 1, 0, newStep); // ํด๋ฆญ๋ ์คํ
์๋ ์ฝ์
+
+ // ์ ์ฒด ์์ ์
๋ฐ์ดํธ
+ const updatedOrderedSteps = updatedSteps.map((step, idx) => ({
+ ...step,
+ recipeStepOrder: idx + 1, // ์์ ์ฌ์ ๋ ฌ
+ }));
+
+ return {
+ ...prevState,
+ recipeSteps: updatedOrderedSteps,
+ };
+ });
+
+ // recipeStepSources์๋ ๋น ๊ฐ ์ถ๊ฐ
+ setRecipeStepSources((prevSources) => {
+ const updatedSources = [...prevSources];
+ updatedSources.splice(index + 1, 0, null); // ํด๋ฆญ๋ ์คํ
์๋์ ๋น ๊ฐ ์ฝ์
+ return updatedSources;
+ });
+
+ };
+
+ const removeStep = (index) => {
+ setRequest((prevState) => {
+ const updatedSteps = [...prevState.recipeSteps];
+ updatedSteps.splice(index, 1); // ํด๋น ์ธ๋ฑ์ค ์คํ
์ญ์
+
+ // ์์ ์ฌ์ ๋ ฌ
+ const updatedOrderedSteps = updatedSteps.map((step, idx) => ({
+ ...step,
+ recipeStepOrder: idx + 1,
+ }));
+
+ return {
+ ...prevState,
+ recipeSteps: updatedOrderedSteps,
+ };
+ });
+
+ // recipeStepSources์์๋ ํด๋น ์ธ๋ฑ์ค์ ์ด๋ฏธ์ง ์ญ์
+ setRecipeStepSources((prevSources) => {
+ const updatedSources = [...prevSources];
+ updatedSources.splice(index, 1); // ํด๋น ์ธ๋ฑ์ค ์ญ์
+ return updatedSources;
+ });
+ };
+
+ const handleStepChange = (e, index) => {
+ const { name, value } = e.target;
+ setRequest(prevState => {
+ const updatedSteps = [...prevState.recipeSteps];
+ updatedSteps[index] = { ...updatedSteps[index], [name]: value };
+ return {
+ ...prevState,
+ recipeSteps: updatedSteps,
+ };
+ });
+ };
+
+ const handleRecipeStepSourcesChange = (e, index) => {
+ const files = Array.from(e.target.files||[]); // ์
๋ก๋๋ ํ์ผ ๋ฆฌ์คํธ
+
+ setRecipeStepSources((prevSources) => {
+ const updatedSources = [...prevSources];
+ updatedSources[index] = files.length > 0 ? files : null; // ํ์ผ์ด ์์ผ๋ฉด ์
๋ฐ์ดํธ, ์์ผ๋ฉด null
+ return updatedSources;
+ });
+ };
+
+ const printRecipeStepSources = () => {
+ console.log(recipeStepSources);
+ }
+ return (
+
+
+
๋ ์ํผ ์ฌ์ง :
+
+
+
+
+
+
+
๋ ์ํผ ์ด๋ฆ :
+
+
+
+
+
+
+
+
+
๋ ์ํผ ๊ฐ๋จํ ๋ด์ฉ :
+
+
+
+
+
+
+
+ {/* ๋จ๊ณ๋ณ ์
๋ ฅ */}
+
+
+ {request.recipeSteps.map((step, index) => (
+
+
์คํ
{index + 1} :
+ handleStepChange(e, index)}
+ placeholder={`์คํ
${index + 1}`}
+ />
+
+ {/* ์จ๊ฒจ์ง ํ์ผ ์ ํ ์
๋ ฅ */}
+
handleRecipeStepSourcesChange(e, index)}
+ style={{display: 'none'}} // ์จ๊ธฐ๊ธฐ
+ />
+
+
+
+
+ {/* ๋ผ๋ฒจ์ ํด๋ฆญํ๋ฉด ํ์ผ ์ฐฝ ์ด๊ธฐ */}
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/sources/api/recipeAPI.jsx b/src/sources/api/recipeAPI.jsx
new file mode 100644
index 0000000..21404ce
--- /dev/null
+++ b/src/sources/api/recipeAPI.jsx
@@ -0,0 +1,18 @@
+import axios from "axios";
+
+export const API_URL_HOST = "http://localhost:8080";
+
+const prefix = `${API_URL_HOST}/recipe`;
+
+export const getRecipeList = async () => {
+ const url = `${prefix}`;
+ const res = await axios.get(url);
+ return res.data;
+}
+
+export const createRecipe = async (recipe) => {
+ console.log(recipe);
+ const url = `${prefix}`;
+ const res = await axios.post(url, recipe);
+ return res.data;
+}
\ No newline at end of file