Skip to content

Commit 0e9e07d

Browse files
committed
Update
1 parent c7c0d2d commit 0e9e07d

109 files changed

Lines changed: 8152 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

static/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

static/.screen-graph.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

static/README.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# 알고리즘 셰프 - Algorithm Chef
2+
3+
환영합니다! 이 프로젝트는 Anima에 의해 자동으로 생성된 React 애플리케이션입니다.
4+
"알고리즘 셰프"는 사용자의 냉장고 속 식재료와 개인 성향을 기반으로 맞춤형 레시피를 추천하고, 식재료 나눔 커뮤니티를 제공하는 서비스입니다.
5+
6+
## 시작하기
7+
8+
> **전제 조건:**
9+
> 다음 단계를 수행하려면 시스템에 [NodeJS](https://nodejs.org/en/)가 설치되어 있어야 합니다.
10+
11+
프로젝트를 시작하려면 먼저 다음 명령어로 의존성을 설치해야 합니다:
12+
13+
```bash
14+
npm install
15+
```
16+
17+
그 다음, 다음 명령어로 개발 버전을 실행할 수 있습니다:
18+
19+
```bash
20+
npm run dev
21+
```
22+
23+
몇 초 후, 프로젝트는 [http://localhost:5173/](http://localhost:5173/) 주소에서 접근할 수 있습니다.
24+
25+
결과에 만족하면, 다음 명령어로 릴리스용 프로젝트를 빌드할 수 있습니다:
26+
27+
```bash
28+
npm run build
29+
```
30+
31+
## 주요 기능
32+
33+
* **반응형 디자인**: 모든 페이지는 다양한 화면 크기(데스크톱, 태블릿, 모바일)에 최적화되어 있습니다.
34+
* **통합 헤더**: 모든 페이지에 일관된 탐색 및 인증 기능을 제공하는 단일 헤더 컴포넌트가 적용되었습니다.
35+
* **메뉴 내비게이션**: 헤더 좌측의 햄버거 메뉴를 통해 주요 페이지(홈, 메뉴 추천, 재료 나눔 게시판, 마이페이지)로 쉽게 이동할 수 있습니다.
36+
* **사용자 인증**:
37+
* 로그인 및 회원가입 팝업 시스템.
38+
* 성별, 생년월일 입력 및 성향(건강 목표, 알레르기, 선호/비선호 재료, 선호 요리, 매운맛 선호도, 알림 설정) 선택 기능.
39+
* 로그인 시 사용자 이름 표시 및 로그아웃 버튼으로 전환.
40+
* 아이디/비밀번호 유효성 검사 및 중복 아이디 확인 (현재 `localStorage` 기반 목업).
41+
* **나의 냉장고**:
42+
* 식재료 목록을 카테고리별로 필터링하고 검색할 수 있습니다.
43+
* 새로운 식재료를 추가하거나 영수증을 등록할 수 있는 기능 (팝업 및 전용 페이지).
44+
* **메뉴 추천**:
45+
* Gemini API와 연동될 검색창 (현재 목업).
46+
* 식재료 기반 및 성향 기반 자동 메뉴 추천 기능.
47+
* 레시피 카드를 최대 12개까지 표시하며, 좌우 스크롤 버튼으로 페이지 이동 가능.
48+
* 각 레시피 카드를 클릭하면 상세 레시피 페이지로 이동합니다.
49+
* **재료 나눔 게시판**:
50+
* 게시글 목록을 페이지네이션(최대 5페이지)으로 탐색할 수 있습니다.
51+
* 로그인한 사용자만 새 글을 작성할 수 있습니다.
52+
* 각 게시글은 고유한 댓글 기능을 가지며, 댓글은 `localStorage`에 저장됩니다.
53+
* 게시글 상세 페이지에서 현재 게시글의 앞뒤 2개씩, 총 4개의 관련 게시글을 표시합니다.
54+
* **마이페이지**:
55+
* 로그인 상태에 따라 사용자 성향 정보를 표시하거나 로그인 요청 메시지를 출력합니다.
56+
* 사용자의 건강 목표, 알레르기, 선호/비선호 재료, 선호 요리, 매운맛 선호도, 알림 설정을 확인할 수 있습니다.
57+
58+
## 백엔드 연동 가이드
59+
60+
이 프로젝트는 프론트엔드 기능 구현에 중점을 두었으며, 모든 데이터는 현재 `localStorage`를 사용하여 목업(mockup)으로 처리됩니다. 실제 백엔드와 연동하려면 다음 지침을 따르세요:
61+
62+
1. **API 클라이언트 설치**: `axios`와 같은 HTTP 클라이언트 라이브러리를 설치합니다.
63+
```bash
64+
npm install axios
65+
```
66+
67+
2. **API 엔드포인트 정의**: 백엔드에서 제공하는 API 엔드포인트를 정의합니다. 예:
68+
* `GET /api/users/{username}/ingredients` (사용자 냉장고 재료)
69+
* `GET /api/ingredients/all` (모든 식재료 DB)
70+
* `POST /api/ingredients` (식재료 추가)
71+
* `POST /api/login` (로그인)
72+
* `POST /api/signup` (회원가입)
73+
* `POST /api/users/{username}/preferences` (사용자 성향 저장)
74+
* `GET /api/users/{username}/preferences` (사용자 성향 조회)
75+
* `GET /api/posts` (모든 게시글)
76+
* `POST /api/posts` (새 게시글 작성)
77+
* `GET /api/posts/{id}/comments` (게시글 댓글 조회)
78+
* `POST /api/posts/{id}/comments` (댓글 작성)
79+
* `GET /api/recommend/ingredients` (식재료 기반 레시피 추천)
80+
* `GET /api/recommend/tendencies` (성향 기반 레시피 추천)
81+
* `POST /api/gemini-search` (Gemini API 검색)
82+
83+
3. **목업 데이터 교체**: 프로젝트 코드 내의 `// TODO: Backend Integration: Replace with API call` 주석을 찾아 `localStorage`를 사용하는 목업 로직을 실제 `axios` 호출로 교체합니다.
84+
85+
**예시 (로그인):**
86+
```javascript
87+
// src/components/LoginPopup/LoginPopup.jsx
88+
const handleLogin = async (e) => {
89+
e.preventDefault();
90+
try {
91+
const response = await axios.post('/api/login', { username, password });
92+
login(response.data.user); // Assuming backend returns user data
93+
onClose();
94+
} catch (error) {
95+
alert("아이디 또는 비밀번호가 일치하지 않습니다.");
96+
console.error("Login error:", error);
97+
}
98+
};
99+
```
100+
101+
**예시 (게시글 목록):**
102+
```javascript
103+
// src/screens/CommunityPage/sections/Communitypage/Communitypage.jsx
104+
useEffect(() => {
105+
const fetchPosts = async () => {
106+
try {
107+
const response = await axios.get('/api/posts');
108+
setAllPosts(response.data);
109+
} catch (error) {
110+
console.error("Failed to fetch posts:", error);
111+
setAllPosts(DEFAULT_POSTS); // Fallback to default posts
112+
}
113+
};
114+
fetchPosts();
115+
}, []);
116+
```
117+
118+
4. **환경 변수 설정**: API의 기본 URL 등은 `.env` 파일을 사용하여 관리하는 것이 좋습니다.
119+
```
120+
VITE_API_BASE_URL=http://localhost:8080/api
121+
```
122+
그리고 코드에서는 `import.meta.env.VITE_API_BASE_URL`과 같이 접근합니다.
123+
124+
이 가이드를 통해 "알고리즘 셰프" 프론트엔드를 실제 백엔드 서비스와 성공적으로 연동할 수 있을 것입니다.

static/index.html

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>알고리즘 셰프 - Algorithm Chef</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
7+
<style>
8+
@import url("https://fonts.googleapis.com/css?family=Inter:500,700,600");
9+
</style>
10+
<style>
11+
@import url("https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css");
12+
* {
13+
-webkit-font-smoothing: antialiased;
14+
box-sizing: border-box;
15+
}
16+
html,
17+
body {
18+
margin: 0px;
19+
height: 100%;
20+
overflow-y: scroll; /* Explicitly enable vertical scrolling */
21+
}
22+
/* a blue color as a generic focus style */
23+
button:focus-visible {
24+
outline: 2px solid #4a90e2 !important;
25+
outline: -webkit-focus-ring-color auto 5px !important;
26+
}
27+
a {
28+
text-decoration: none;
29+
}
30+
</style>
31+
</head>
32+
<body>
33+
<div id="app"></div>
34+
<script type="module" src="./src/index.jsx"></script>
35+
</body>
36+
</html>

static/package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "anima-project",
3+
"version": "1.0.0",
4+
"description": "A React project automatically generated by Anima, the design-to-code platform",
5+
"source": "./index.html",
6+
"type": "module",
7+
"scripts": {
8+
"dev": "vite",
9+
"build": "vite build"
10+
},
11+
"dependencies": {
12+
"react": "^18.2.0",
13+
"react-dom": "^18.2.0",
14+
"react-router-dom": "^6.8.1"
15+
},
16+
"devDependencies": {
17+
"@animaapp/vite-plugin-screen-graph": "^0.1.5",
18+
"@vitejs/plugin-react": "4.3.4",
19+
"esbuild": "0.24.0",
20+
"globals": "15.12.0",
21+
"vite": "6.0.4"
22+
}
23+
}

static/src/App.jsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from "react";
2+
import { RouterProvider, createBrowserRouter } from "react-router-dom";
3+
import { AuthProvider } from "./contexts/AuthContext";
4+
import { ScrollToTop } from "./components/ScrollToTop"; // Import ScrollToTop
5+
import { CommunityContent } from "./screens/CommunityContent";
6+
import { CommunityPage } from "./screens/CommunityPage";
7+
import { Desktop } from "./screens/Desktop";
8+
import { MyPage } from "./screens/MyPage";
9+
import { RecipePage } from "./screens/RecipePage";
10+
import { AddIngredientPage } from "./screens/AddIngredientPage";
11+
import { CreatePostPage } from "./screens/CreatePostPage";
12+
import { MenuRecommendationPage } from "./screens/MenuRecommendationPage";
13+
14+
const router = createBrowserRouter([
15+
{
16+
path: "/*",
17+
element: <Desktop />,
18+
},
19+
{
20+
path: "/desktop",
21+
element: <Desktop />,
22+
},
23+
{
24+
path: "/communitypage",
25+
element: <CommunityPage />,
26+
},
27+
{
28+
path: "/mypage",
29+
element: <MyPage />,
30+
},
31+
{
32+
path: "/recipepage/:id",
33+
element: <RecipePage />,
34+
},
35+
{
36+
path: "/communitycontentpage/:id",
37+
element: <CommunityContent />,
38+
},
39+
{
40+
path: "/addingredient",
41+
element: <AddIngredientPage />,
42+
},
43+
{
44+
path: "/createpost",
45+
element: <CreatePostPage />,
46+
},
47+
{
48+
path: "/menurecommendation",
49+
element: <MenuRecommendationPage />,
50+
},
51+
]);
52+
53+
export const App = () => {
54+
return (
55+
<AuthProvider>
56+
<RouterProvider router={router}>
57+
<ScrollToTop /> {/* Render ScrollToTop inside RouterProvider */}
58+
</RouterProvider>
59+
</AuthProvider>
60+
);
61+
};
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import React, { useState } from "react";
2+
import "./style.css";
3+
4+
const CATEGORIES = ["육류", "채소류", "가공류", "유제품", "기타"];
5+
6+
export const AddIngredientPopup = ({ onClose, onAdd }) => {
7+
const [name, setName] = useState("");
8+
const [category, setCategory] = useState("채소류");
9+
const [expiryDays, setExpiryDays] = useState("");
10+
11+
const handleSubmit = (e) => {
12+
e.preventDefault();
13+
14+
if (!name || !expiryDays) {
15+
alert("모든 필드를 입력해주세요.");
16+
return;
17+
}
18+
19+
const newIngredient = {
20+
id: Date.now(),
21+
name,
22+
category,
23+
expiryDays: parseInt(expiryDays),
24+
image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png"
25+
};
26+
27+
// TODO: Send to backend
28+
console.log("Adding ingredient via receipt:", newIngredient);
29+
onAdd(newIngredient);
30+
onClose();
31+
};
32+
33+
return (
34+
<div className="add-ingredient-popup-overlay" onClick={onClose}>
35+
<div className="add-ingredient-popup-container" onClick={(e) => e.stopPropagation()}>
36+
<button className="add-ingredient-popup-close" onClick={onClose}>×</button>
37+
38+
<div className="add-ingredient-popup-header">
39+
<h2 className="add-ingredient-popup-title">식재료 추가</h2>
40+
</div>
41+
42+
<form className="add-ingredient-popup-form" onSubmit={handleSubmit}>
43+
<div className="add-ingredient-popup-input-group">
44+
<label className="add-ingredient-popup-label">식재료 이름</label>
45+
<input
46+
type="text"
47+
className="add-ingredient-popup-input"
48+
value={name}
49+
onChange={(e) => setName(e.target.value)}
50+
placeholder="예: 양파"
51+
required
52+
/>
53+
</div>
54+
55+
<div className="add-ingredient-popup-input-group">
56+
<label className="add-ingredient-popup-label">카테고리</label>
57+
<select
58+
className="add-ingredient-popup-select"
59+
value={category}
60+
onChange={(e) => setCategory(e.target.value)}
61+
required
62+
>
63+
{CATEGORIES.map((cat) => (
64+
<option key={cat} value={cat}>{cat}</option>
65+
))}
66+
</select>
67+
</div>
68+
69+
<div className="add-ingredient-popup-input-group">
70+
<label className="add-ingredient-popup-label">유통기한 (일)</label>
71+
<input
72+
type="number"
73+
className="add-ingredient-popup-input"
74+
value={expiryDays}
75+
onChange={(e) => setExpiryDays(e.target.value)}
76+
placeholder="예: 7"
77+
min="1"
78+
required
79+
/>
80+
</div>
81+
82+
<button type="submit" className="add-ingredient-popup-submit-btn">
83+
추가하기
84+
</button>
85+
</form>
86+
</div>
87+
</div>
88+
);
89+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { AddIngredientPopup } from "./AddIngredientPopup";

0 commit comments

Comments
 (0)