Skip to content

Commit 01901f1

Browse files
committed
게시글 댓글 기능 업데이트
1 parent 997b68c commit 01901f1

13 files changed

Lines changed: 208 additions & 67 deletions

File tree

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
body {
1818
margin: 0px;
1919
height: 100%;
20+
overflow-y: scroll; /* Explicitly enable vertical scrolling */
2021
}
2122
/* a blue color as a generic focus style */
2223
button:focus-visible {

src/App.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from "react";
22
import { RouterProvider, createBrowserRouter } from "react-router-dom";
33
import { AuthProvider } from "./contexts/AuthContext";
4+
import { ScrollToTop } from "./components/ScrollToTop"; // Import ScrollToTop
45
import { CommunityContent } from "./screens/CommunityContent";
56
import { CommunityPage } from "./screens/CommunityPage";
67
import { Desktop } from "./screens/Desktop";
@@ -52,7 +53,9 @@ const router = createBrowserRouter([
5253
export const App = () => {
5354
return (
5455
<AuthProvider>
55-
<RouterProvider router={router} />
56+
<RouterProvider router={router}>
57+
<ScrollToTop /> {/* Render ScrollToTop inside RouterProvider */}
58+
</RouterProvider>
5659
</AuthProvider>
5760
);
5861
};

src/components/LoginPopup/LoginPopup.jsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,36 @@ import { useAuth } from "../../contexts/AuthContext";
33
import "./style.css";
44

55
export const LoginPopup = ({ onClose, onSwitchToSignup }) => {
6-
const [username, setUsername] = useState("");
6+
const [email, setEmail] = useState(""); // Changed to email
77
const [password, setPassword] = useState("");
88
const { login } = useAuth();
99

1010
const handleLogin = async (e) => {
1111
e.preventDefault();
1212

1313
// TODO: Backend Integration: Replace with actual backend API call for login
14-
// Example: axios.post('/api/login', { username, password })
14+
// Example: axios.post('/api/login', { email, password })
1515
// .then(response => {
16-
// login(response.data.user);
16+
// login(response.data.user); // Assuming backend returns user data including username
1717
// onClose();
1818
// })
1919
// .catch(error => {
20-
// alert("아이디 또는 비밀번호가 일치하지 않습니다.");
20+
// alert("이메일 또는 비밀번호가 일치하지 않습니다.");
2121
// });
2222

2323
// Simulating backend validation with localStorage
2424
const mockUsers = JSON.parse(localStorage.getItem("registeredUsers") || "[]");
2525
const foundUser = mockUsers.find(
26-
(u) => u.username === username && u.password === password
26+
(u) => u.email === email && u.password === password // Validate by email
2727
);
2828

2929
if (foundUser) {
30-
const userData = { username: foundUser.username };
30+
const userData = { username: foundUser.username, email: foundUser.email }; // Store username for display
3131
login(userData);
3232
console.log("Login successful:", userData);
3333
onClose();
3434
} else {
35-
alert("아이디 또는 비밀번호가 일치하지 않습니다.");
35+
alert("이메일 또는 비밀번호가 일치하지 않습니다.");
3636
}
3737
};
3838

@@ -47,13 +47,13 @@ export const LoginPopup = ({ onClose, onSwitchToSignup }) => {
4747

4848
<form className="login-popup-form" onSubmit={handleLogin}>
4949
<div className="login-popup-input-group">
50-
<label className="login-popup-label">아이디</label>
50+
<label className="login-popup-label">이메일</label> {/* Changed label */}
5151
<input
52-
type="text"
52+
type="email" // Changed type to email
5353
className="login-popup-input"
54-
value={username}
55-
onChange={(e) => setUsername(e.target.value)}
56-
placeholder="아이디를 입력하세요"
54+
value={email} // Changed value to email
55+
onChange={(e) => setEmail(e.target.value)} // Changed onChange to setEmail
56+
placeholder="이메일을 입력하세요" // Changed placeholder
5757
required
5858
/>
5959
</div>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useLayoutEffect } from 'react';
2+
import { useLocation } from 'react-router-dom';
3+
4+
export const ScrollToTop = () => {
5+
const { pathname } = useLocation();
6+
7+
useLayoutEffect(() => {
8+
const scrollToTop = () => {
9+
// Attempt to scroll immediately with instant behavior
10+
window.scrollTo({ top: 0, behavior: 'instant' });
11+
document.documentElement.scrollTo({ top: 0, behavior: 'instant' });
12+
document.body.scrollTo({ top: 0, behavior: 'instant' });
13+
};
14+
15+
scrollToTop(); // Run once immediately
16+
17+
// Add a small timeout as a fallback for stubborn cases
18+
// This ensures it runs after any potential immediate DOM updates
19+
const timer = setTimeout(scrollToTop, 0);
20+
21+
return () => clearTimeout(timer); // Clean up the timer
22+
}, [pathname]);
23+
24+
return null;
25+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ScrollToTop } from "./ScrollToTop";

src/components/SignupPopup/SignupPopup.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ export const SignupPopup = ({ onClose, onSwitchToLogin, onSwitchToPreferences })
2929
// Simulating backend validation and storage with localStorage
3030
const mockUsers = JSON.parse(localStorage.getItem("registeredUsers") || "[]");
3131

32-
// Check if username already exists
33-
if (mockUsers.find((u) => u.username === username)) {
34-
alert("이미 존재하는 아이디입니다.");
32+
// Check if email already exists
33+
if (mockUsers.find((u) => u.email === email)) { // Check for duplicate email
34+
alert("이미 존재하는 이메일입니다.");
3535
return;
3636
}
3737

src/components/UnifiedHeader/UnifiedHeader.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export const UnifiedHeader = () => {
8686
</Link>
8787
{isAuthenticated ? (
8888
<div className="unified-user-info">
89-
<span className="unified-username">{user?.username}</span>
89+
<span className="unified-username">{user?.username}</span> {/* Added "님" */}
9090
<button className="unified-logout-btn" onClick={logout}>
9191
로그아웃
9292
</button>

src/screens/AddIngredientPage/AddIngredientPage.jsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { useState } from "react";
2-
import { Link, useNavigate } from "react-router-dom";
1+
import React, { useState, useEffect } from "react";
2+
import { Link, useNavigate } from "react-router-dom"; // Removed useLocation
33
import { UnifiedHeader } from "../../components/UnifiedHeader";
44
import "./style.css";
55

@@ -27,6 +27,10 @@ const MOCK_INGREDIENT_DATABASE = [
2727

2828
export const AddIngredientPage = () => {
2929
const navigate = useNavigate();
30+
// Removed useLocation and onAddMultipleIngredients here
31+
// const location = useLocation();
32+
// const onAddMultipleIngredients = location.state?.onAddMultipleIngredients;
33+
3034
const [selectedCategory, setSelectedCategory] = useState("전체");
3135
const [searchQuery, setSearchQuery] = useState("");
3236
const [selectedIngredients, setSelectedIngredients] = useState([]);
@@ -43,15 +47,26 @@ export const AddIngredientPage = () => {
4347
if (exists) {
4448
return prev.filter((item) => item.id !== ingredient.id);
4549
} else {
46-
return [...prev, { ...ingredient, expiryDays: 7 }];
50+
return [...prev, { ...ingredient, expiryDays: 7 }]; // Default expiry days
4751
}
4852
});
4953
};
5054

5155
const handleAddIngredients = () => {
52-
// TODO: Send selected ingredients to backend
53-
console.log("Adding ingredients:", selectedIngredients);
54-
navigate("/desktop");
56+
if (selectedIngredients.length === 0) {
57+
alert("추가할 식재료를 선택해주세요.");
58+
return;
59+
}
60+
61+
// Directly update localStorage for userIngredients
62+
const currentIngredients = JSON.parse(localStorage.getItem("userIngredients") || "[]");
63+
const updatedIngredients = [...currentIngredients, ...selectedIngredients];
64+
localStorage.setItem("userIngredients", JSON.stringify(updatedIngredients));
65+
66+
// TODO: Backend Integration: Send selectedIngredients to backend
67+
console.log("Adding ingredients to mock DB:", selectedIngredients);
68+
69+
navigate("/desktop"); // Navigate back to desktop
5570
};
5671

5772
return (

src/screens/CommunityContent/sections/Communitycontentpage/Communitycontentpage.jsx

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import React, { useState } from "react";
1+
import React, { useState, useEffect } from "react";
22
import { Link, useLocation, useParams } from "react-router-dom";
33
import { useAuth } from "../../../../contexts/AuthContext";
44
import { CommunitypageWrapper } from "../../../../components/CommunitypageWrapper";
5-
import { PageButton } from "../../../../components/PageButton";
65
import "./style.css";
76

8-
const ALL_POSTS = [
9-
// TODO: Backend Integration: Replace with API call to fetch all community posts
7+
// Default posts for fallback if localStorage is empty
8+
const DEFAULT_POSTS = [
109
{ id: 1, title: "돼지고기 100g 나눔합니다.", author: "test3User", date: "2025-11-03", category: "나눔", content: "돼지고기를 너무 많이 샀네요 남는 돼지고기 나눔해요" },
1110
{ id: 2, title: "양파 2개 나눔해요", author: "user123", date: "2025-11-02", category: "나눔", content: "양파 2개 필요하신 분 가져가세요" },
1211
{ id: 3, title: "닭고기 500g 나눔", author: "foodlover", date: "2025-11-01", category: "나눔", content: "신선한 닭고기 나눔합니다" },
@@ -24,35 +23,46 @@ export const Communitycontentpage = () => {
2423
const { id } = useParams();
2524
const { user, isAuthenticated } = useAuth();
2625
const [newComment, setNewComment] = useState("");
27-
28-
// Load comments from localStorage for this specific post
29-
const [comments, setComments] = useState(() => {
26+
const [allCommunityPosts, setAllCommunityPosts] = useState([]);
27+
28+
// Load comments from localStorage for this specific post, and re-load when 'id' changes
29+
const [comments, setComments] = useState([]);
30+
31+
useEffect(() => {
3032
const storedComments = localStorage.getItem(`comments_${id}`);
31-
return storedComments ? JSON.parse(storedComments) : [];
32-
});
33-
34-
const post = location.state?.post || {
35-
// TODO: Backend Integration: Fetch post details by ID if not available in state
36-
id: id,
37-
title: "돼지고기 100g 나눔합니다.",
38-
author: "test3User",
39-
date: "2025-11-03",
40-
category: "나눔",
41-
content: "돼지고기를 너무 많이 샀네요 남는 돼지고기 나눔해요"
42-
};
33+
setComments(storedComments ? JSON.parse(storedComments) : []);
34+
}, [id]); // Dependency array includes 'id'
35+
36+
useEffect(() => {
37+
// Load all community posts from localStorage to find related posts
38+
const storedPosts = JSON.parse(localStorage.getItem("communityPosts") || "[]");
39+
const combined = [...storedPosts, ...DEFAULT_POSTS];
40+
setAllCommunityPosts(combined);
41+
}, []); // This effect runs only once on mount
42+
43+
const post = location.state?.post ||
44+
allCommunityPosts.find(p => p.id === parseInt(id)) || // Try to find post from allCommunityPosts
45+
{ // Fallback if not found
46+
id: id,
47+
title: "게시글을 찾을 수 없습니다.",
48+
author: "알 수 없음",
49+
date: "",
50+
category: "",
51+
content: "해당 게시글을 불러오는 데 실패했습니다."
52+
};
4353

4454
// Get related posts (2 before and 2 after current post)
45-
const currentIndex = ALL_POSTS.findIndex(p => p.id === parseInt(id));
55+
const currentIndex = allCommunityPosts.findIndex(p => p.id === parseInt(id));
4656
const relatedPosts = [];
4757

4858
// Get 2 posts before
4959
for (let i = Math.max(0, currentIndex - 2); i < currentIndex; i++) {
50-
if (ALL_POSTS[i]) relatedPosts.push(ALL_POSTS[i]);
60+
if (allCommunityPosts[i]) relatedPosts.push(allCommunityPosts[i]);
5161
}
5262

5363
// Get 2 posts after
54-
for (let i = currentIndex + 1; i <= Math.min(ALL_POSTS.length - 1, currentIndex + 2); i++) {
55-
if (ALL_POSTS[i]) relatedPosts.push(ALL_POSTS[i]);
64+
for (let i = currentIndex + 1; i <= Math.min(allCommunityPosts.length - 1, currentIndex + 2); i++) {
65+
if (allCommunityPosts[i]) relatedPosts.push(allCommunityPosts[i]);
5666
}
5767

5868
const handleCommentSubmit = (e) => {

src/screens/Desktop/Desktop.jsx

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,70 @@
1-
import React from "react";
1+
import React, { useState, useEffect } from "react";
2+
import { useLocation } from "react-router-dom"; // Import useLocation
23
import { UnifiedHeader } from "../../components/UnifiedHeader";
34
import { Community } from "./sections/Community";
45
import { Footer } from "./sections/Footer";
56
import { IngredientInventory } from "./sections/IngredientInventory";
67
import { MenuInventory } from "./sections/MenuInventory";
78
import "./style.css";
89

10+
const INITIAL_INGREDIENTS = [
11+
{ id: 1, name: "양파", category: "채소류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-1.png", expiryDays: 5 },
12+
{ id: 2, name: "닭고기", category: "육류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 3 },
13+
{ id: 3, name: "고추장", category: "가공류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 30 },
14+
{ id: 4, name: "고춧가루", category: "가공류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 60 },
15+
{ id: 5, name: "양배추", category: "채소류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 7 },
16+
{ id: 6, name: "스테비아", category: "가공류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 90 },
17+
{ id: 7, name: "마늘", category: "채소류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 14 },
18+
];
19+
920
export const Desktop = () => {
21+
const [ingredients, setIngredients] = useState([]);
22+
const location = useLocation(); // Initialize useLocation
23+
24+
useEffect(() => {
25+
// This effect will run on mount and when location changes,
26+
// ensuring fresh data when returning from AddIngredientPage
27+
const storedIngredients = JSON.parse(localStorage.getItem("userIngredients") || "[]");
28+
if (storedIngredients.length > 0) {
29+
setIngredients(storedIngredients);
30+
} else {
31+
setIngredients(INITIAL_INGREDIENTS);
32+
localStorage.setItem("userIngredients", JSON.stringify(INITIAL_INGREDIENTS));
33+
}
34+
}, [location.pathname]); // Re-run when pathname changes
35+
36+
const handleAddIngredient = (newIngredient) => {
37+
setIngredients((prevIngredients) => {
38+
const updatedIngredients = [...prevIngredients, newIngredient];
39+
localStorage.setItem("userIngredients", JSON.stringify(updatedIngredients));
40+
// TODO: Backend Integration: Send newIngredient to backend
41+
console.log("Added ingredient to mock DB:", newIngredient);
42+
return updatedIngredients;
43+
});
44+
};
45+
46+
const handleAddMultipleIngredients = (newIngredients) => {
47+
setIngredients((prevIngredients) => {
48+
const updatedIngredients = [...prevIngredients, ...newIngredients];
49+
localStorage.setItem("userIngredients", JSON.stringify(updatedIngredients));
50+
// TODO: Backend Integration: Send newIngredients to backend
51+
console.log("Added multiple ingredients to mock DB:", newIngredients);
52+
return updatedIngredients;
53+
});
54+
};
55+
1056
return (
1157
<div className="desktop" data-model-id="45:2">
1258
<UnifiedHeader />
1359
<div className="site-header-name">
1460
<div className="choose-your-receipt">오늘의 메뉴를 골라보세요</div>
1561
</div>
1662

17-
<IngredientInventory />
63+
<IngredientInventory
64+
ingredients={ingredients}
65+
onAddIngredient={handleAddIngredient}
66+
onAddMultipleIngredients={handleAddMultipleIngredients}
67+
/>
1868
<MenuInventory />
1969
<Community />
2070
<Footer />

0 commit comments

Comments
 (0)