Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Join from "./pages/user/Join"
import Login from "./pages/user/Login"
import RecipeRecommend from "./pages/recipe/RecipeRecommend"
import {UpdateRecipe} from "./pages/recipe/UpdateRecipe.jsx"
import RecipeLiked from "./pages/recipe/RecipeLiked.jsx"
import Logout from "./components/user/Logout"
import PublicOnlyRoute from "./components/auth/PublicOnlyRoute"
import RecipeRandom from "./pages/recipe/RecipeRandom"
Expand All @@ -23,6 +24,7 @@ function App() {
</Route>
<Route path="recipe" >
<Route index element={<Recipe/>} />
<Route path='liked/:userPk' element={<RecipeLiked/>} />
<Route path='create' element={<AddRecipe/>} />
<Route path='recommend/:userPk' element={<RecipeRecommend/>} />
<Route path=':recipePk' element={<RecipeDetail/>}/>
Expand Down
33 changes: 33 additions & 0 deletions src/assets/css/recipe/Recipe.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.recipewrapper {
display: flex;
max-width: 880px; /* 최대 너비 992px */
margin: 0 auto;
gap: px;
flex-wrap: wrap; /* 줄 바꿈 허용 */
justify-content:space-between;; /* 카드 간의 간격 조절 */
gap: 16px; /* 카드 간의 간격 */
padding: 8px; /* 컨테이너 내부 여백 */
}
.title {
text-align: center;
}
.actions {
display: flex;
width: 880px;
}
.actions input {
border-radius: 5px;
flex-grow: 1;
margin-right: 20px;
border: 1px solid rgb(220,220,220);
padding:10px 0px;
}
.actions input:focus {
outline: none;
}
.actions button{
padding:5px;
margin:0px 5px;
border-radius: 5px;
border: 1px solid rgb(220,220,220);
}
50 changes: 50 additions & 0 deletions src/assets/css/recipe/RecipeDetail.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

.imgdivcontainer {
display:flex;
margin:0 auto;
width: 992px;
height: 400px;
}
.imgdiv{
margin:20px 0px;
text-align: center;
padding: 30px 30px;
border: 1px solid rgb(220,220,220);
border-radius: 10px;
width: 400px;
height: 360px;
}
.information {
margin:20px;
margin-right: 0px;
flex-grow:1;
border: 1px solid rgb(220,220,220);
border-radius: 10px;
padding:40px 30px;
}
.information p{
margin:5px;
}

.information div{
text-align: right;
}
.information button{
padding:5px;
border: 1px solid rgb(220,220,220);
border-radius: 5px;
}

.container {
text-align: center;
padding: 40px 50px;
border: 1px solid rgb(220,220,220);
border-radius: 10px;
max-width: 992px;
margin:0px auto;
margin-bottom: 50px;
}
.container h2 {
margin-bottom:30px;
}

27 changes: 27 additions & 0 deletions src/assets/css/recipe/RecipeItem.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.card {
width: 200px;
height: 300px;
padding: 30px;
background-color: #f0f0f0; /* 카드 배경색 */
border: 1px solid #ccc; /* 카드 테두리 */
border-radius: 8px; /* 카드 모서리 둥글게 */
display: flex; /* 카드 내용을 정렬할 때 유용 */
flex-direction: column;

}
.card:hover {
background-color: #e0e0e0;
}
.card p {
text-align: left;
margin: 0; /* p 태그의 기본 margin을 제거하여 깔끔하게 왼쪽 정렬 */
}
.card h3 {
text-align: center;
}
.card img {
margin:auto;
width: 100%; /* 카드 너비에 맞게 이미지 크기 조정 */
height: auto; /* 이미지 비율 유지 */
object-fit: cover; /* 이미지 비율을 유지하면서 공간을 채움 */
}
4 changes: 0 additions & 4 deletions src/assets/css/recipe/recipe.css

This file was deleted.

46 changes: 41 additions & 5 deletions src/components/recipe/recipeItem.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,51 @@
import defaultRecipeImg from "../../assets/image/recipeimage.png"
import style from "../../assets/css/recipe/RecipeItem.module.css"
import { useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
import axios from 'axios'

const RecipeItem = ({ recipe,userPk }) => {
const navigate = useNavigate()

const RecipeItem = ({ recipe }) => {
const imgSrc = recipe.recipeSource.length!==0?recipe.recipeSource[0].recipeSourceSave:defaultRecipeImg
const toRecipe = ()=>{
navigate(`/recipe/${recipe.recipePk}`)
}
const [liked, setLiked] = useState(false); // 초기 좋아요 상태
const handleToggleLike = async (e) => {
e.stopPropagation()
try {
const response = await axios.post(`http://localhost:8080/recipe/reaction`, {
recipePk:recipe.recipePk,
userPk:userPk,
likeStatus:false
});
console.log(response.data)
setLiked(!liked)
} catch (error) {
console.error("Failed to toggle like:", error);
}
};
return (
<div >
<div onClick={toRecipe} className={style.card}>
<h3>{recipe.recipeName}</h3>
<img src={`${imgSrc}`} alt="Recipe Image" style={{ width: '100px', height: 'auto' }} />
<h3>이름:{recipe.recipeName}</h3>
<p>내용:{recipe.recipeContent}</p>
<p>조리시간:{recipe.recipeCookingTime}(단위)</p>
<p>{recipe.recipeContent}</p>
<p>조리시간:{recipe.recipeCookingTime}분</p>
<p>난이도:{recipe.recipeDifficulty}</p>
<p>조회수:{recipe.recipeViews}</p>
<button
onClick={handleToggleLike}
style={{
backgroundColor:"gray",
color: "white",
border: "none",
padding: "10px",
cursor: "pointer",
}}
>
{liked ? "Liked ❤️" : "Like 🤍"}
</button>
</div>
);
};
Expand Down
48 changes: 30 additions & 18 deletions src/pages/recipe/Recipe.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { getRecipeList } from "../../sources/api/recipeAPI.jsx";
import {Link, Route, useNavigate} from "react-router-dom";
import { AddRecipe} from "./addRecipe.jsx";
import RecipeItem from "../../components/recipe/RecipeItem.jsx"
import "../../assets/css/recipe/recipe.css"
import style from "../../assets/css/recipe/recipe.module.css"


function Recipe() {
const [recipeList, setRecipeList] = useState([]);
const [recipeList, setRecipeList] = useState([])
const [search, setSearch] = useState("")
const navigate = useNavigate();
const [userPk, setUserPk] = useState(null);

Expand Down Expand Up @@ -43,7 +44,16 @@ function Recipe() {

checkLogin();
}, [navigate]);

const onChangeSearch = (e)=>{
setSearch(e.target.value)
}
const getFilterdData = () =>{
if(search===""){
return recipeList
}
return recipeList.filter((recipe=>recipe.recipeName.includes(search)))
}
const filteredRecipeList = getFilterdData()
useEffect(() => {
const fetchRecipes = async () => {
try {
Expand All @@ -69,23 +79,25 @@ function Recipe() {
navigate("/recipe/create");
}, [navigate, userPk]);

const handleRecommand = useCallback(() => {
navigate(`/recipe/recommend/${userPk}`);
},[navigate,userPk])
const handleLike = useCallback(() => {
navigate(`/recipe/liked/${userPk}`);
},[navigate,userPk])

return (
<>
<h1>레시피 페이지</h1>
<div>
<button onClick={handleAdd}>레시피 추가</button>
{userPk && (
<Link
to={`/recipe/recommend/${userPk}`}
className="ml-4 px-4 py-2 bg-blue-500 text-white rounded"
>
맞춤 레시피 보기
</Link>
)}
</div>
<div className="recipe-wrapper">
{recipeList.map((recipe) => (
<RecipeItem key={recipe.recipePk} recipe={recipe} />
<h1 className={style.title}>레시피 페이지</h1>
<div className={style.recipewrapper}>
<div className={style.actions}>
<input value={search} onChange={onChangeSearch} placeholder="검색" />
<button onClick={handleAdd}>레시피 추가</button>
<button onClick={handleRecommand}>맞춤 레시피 보기</button>
<button onClick={handleLike} >좋아요한 레시피 보기</button>
</div>
{filteredRecipeList.map((recipe) => (
<RecipeItem key={recipe.recipePk} recipe={recipe} userPk={userPk}/>
))}
</div>
</>
Expand Down
60 changes: 42 additions & 18 deletions src/pages/recipe/RecipeDetail.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { useParams } from 'react-router-dom'
import { useParams,useNavigate } from 'react-router-dom'
import { useEffect, useState,useCallback } from "react"
import { getRecipeDetail } from "../../sources/api/recipeAPI.jsx";
import { getRecipeDetail,deleteRecipe } from "../../sources/api/recipeAPI.jsx";
import defaultRecipeImg from "../../assets/image/recipeimage.png"

import style from "../../assets/css/recipe/RecipeDetail.module.css"

function RecipeDetail() {
const { recipePk } = useParams()
const [recipeDetail, setRecipeDetail] = useState({})
const navigate = useNavigate();
const toUpdate = useCallback(() => {
navigate(`/recipe/${recipePk}/update`);
},[navigate])
const toDelete = useCallback(() => {
const handleDelete = async () => {
try {
await deleteRecipe(recipePk);
navigate("/recipe");
} catch (err) {
console.error(err);
}
};
handleDelete(); // 비동기 함수 호출
}, [recipePk, navigate]);
useEffect(() => {
const fetchRecipes = async () => {
try {
Expand All @@ -20,37 +35,46 @@ function RecipeDetail() {
fetchRecipes();
}, [recipePk]);

const recipeImage =
const imgSrc =
recipeDetail.recipeSource && recipeDetail.recipeSource.length > 0
? recipeDetail.recipeSource[0]?.recipeSourceSave
: defaultRecipeImg;

return (
<>
<h1>레시피 상세조회 페이지</h1>
<div>
<img src={recipeImage} alt="레시피 이미지" />
<h3>이름:{recipeDetail.recipeName}</h3>
<p>내용:{recipeDetail.recipeContent}</p>
<p>조리시간:{recipeDetail.recipeCookingTime}(단위)</p>
<p>난이도:{recipeDetail.recipeDifficulty}</p>
<p>조회수:{recipeDetail.recipeViews}</p>
<p>재료</p>
{recipeDetail.ingredients && recipeDetail.ingredients.map(ingredient => (<p key={ingredient?.ingredientManagement.ingredientManagementPk}>{ingredient.ingredientManagement.ingredientName}</p>))}
<p>요리과정</p>
{recipeDetail.recipeStep &&
<div className={style.imgdivcontainer}>
<div className={style.imgdiv}>
<h1>{recipeDetail.recipeName}</h1>
<img src={`${imgSrc}`} alt="Recipe Image"/>
</div>
<div className={style.information}>
<div>
<button>좋아요</button>
<button onClick={toUpdate}>수정</button>
<button onClick={toDelete}>삭제</button>
</div>
<p>내용 {recipeDetail.recipeContent}</p>
<p>조리시간 {recipeDetail.recipeCookingTime}분</p>
<p>난이도 {recipeDetail.recipeDifficulty}</p>
<p>조회수 {recipeDetail.recipeViews}</p>
<p>재료</p>
{recipeDetail.ingredients && recipeDetail.ingredients.map(ingredient => (<p key={ingredient.ingredientPk}>{ingredient.ingredientName}</p>))}
</div>
</div>
<div className={style.container}>
<h2>요리과정</h2>
{recipeDetail.recipeStep &&
recipeDetail.recipeStep.map((step, index) => (
<div key={step?.recipeStepOrder}>
<p>Step {index + 1}: {step.recipeStepContent}</p>
{step.recipeStepSource && (
<img
src={step.recipeStepSource}
src={step.recipeStepSource.recipeStepSourceSave}
alt={`Step ${index + 1}`}
/>
)}
</div>
))}

</div>
</>
)
Expand Down
33 changes: 33 additions & 0 deletions src/pages/recipe/RecipeLiked.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useParams,useNavigate } from 'react-router-dom'
import { useState,useEffect } from 'react'
import { getLikedRecipes } from '../../sources/api/recipeAPI';
import RecipeItem from '../../components/recipe/RecipeItem';
import style from "../../assets/css/recipe/recipe.module.css"
function RecipeLiked() {
const { userPk } = useParams()
const [likedRecipeList, setLikedRecipeList] = useState([])
useEffect(() => {
const fetchRecipes = async () => {
try {
const data = await getLikedRecipes(userPk);

console.log("LikedRecipeList",data); // 가져온 데이터 확인
setLikedRecipeList(data); // 상태 업데이트
} catch (err) {
console.error(err);
}
};
fetchRecipes(); // 비동기 작업 호출
}, []);
return(
<>
<h1 className={style.title}>좋아요한 레시피 페이지</h1>
<div className={style.recipewrapper}>
{likedRecipeList.map((recipe) => (
<RecipeItem key={recipe.recipePk} recipe={recipe} />
))}
</div>
</>
)
}
export default RecipeLiked
Loading