diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 0c91180..9da9239 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -2,9 +2,9 @@ name: Frontend CI (Vite Build) on: push: - branches: [ "main" ] # 귣ġ ̸ master ʿ + branches: [ "**" ] # 귣ġ ̸ master ʿ pull_request: - branches: [ "main" ] + branches: [ "**" ] jobs: build-check: diff --git a/index.html b/index.html index 6ba856d..ad21cc5 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,7 @@ body { margin: 0px; height: 100%; + overflow-y: scroll; /* Explicitly enable vertical scrolling */ } /* a blue color as a generic focus style */ button:focus-visible { diff --git a/src/App.jsx b/src/App.jsx index 5c31980..ece6972 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,7 @@ import React from "react"; import { RouterProvider, createBrowserRouter } from "react-router-dom"; import { AuthProvider } from "./contexts/AuthContext"; +import { ScrollToTop } from "./components/ScrollToTop"; // Import ScrollToTop import { CommunityContent } from "./screens/CommunityContent"; import { CommunityPage } from "./screens/CommunityPage"; import { Desktop } from "./screens/Desktop"; @@ -52,7 +53,9 @@ const router = createBrowserRouter([ export const App = () => { return ( - + + {/* Render ScrollToTop inside RouterProvider */} + ); }; diff --git a/src/components/LoginPopup/LoginPopup.jsx b/src/components/LoginPopup/LoginPopup.jsx index 8d4cf23..9e29ed3 100644 --- a/src/components/LoginPopup/LoginPopup.jsx +++ b/src/components/LoginPopup/LoginPopup.jsx @@ -3,7 +3,7 @@ import { useAuth } from "../../contexts/AuthContext"; import "./style.css"; export const LoginPopup = ({ onClose, onSwitchToSignup }) => { - const [username, setUsername] = useState(""); + const [email, setEmail] = useState(""); // Changed to email const [password, setPassword] = useState(""); const { login } = useAuth(); @@ -11,28 +11,28 @@ export const LoginPopup = ({ onClose, onSwitchToSignup }) => { e.preventDefault(); // TODO: Backend Integration: Replace with actual backend API call for login - // Example: axios.post('/api/login', { username, password }) + // Example: axios.post('/api/login', { email, password }) // .then(response => { - // login(response.data.user); + // login(response.data.user); // Assuming backend returns user data including username // onClose(); // }) // .catch(error => { - // alert("아이디 또는 비밀번호가 일치하지 않습니다."); + // alert("이메일 또는 비밀번호가 일치하지 않습니다."); // }); // Simulating backend validation with localStorage const mockUsers = JSON.parse(localStorage.getItem("registeredUsers") || "[]"); const foundUser = mockUsers.find( - (u) => u.username === username && u.password === password + (u) => u.email === email && u.password === password // Validate by email ); if (foundUser) { - const userData = { username: foundUser.username }; + const userData = { username: foundUser.username, email: foundUser.email }; // Store username for display login(userData); console.log("Login successful:", userData); onClose(); } else { - alert("아이디 또는 비밀번호가 일치하지 않습니다."); + alert("이메일 또는 비밀번호가 일치하지 않습니다."); } }; @@ -47,13 +47,13 @@ export const LoginPopup = ({ onClose, onSwitchToSignup }) => {
- + {/* Changed label */} setUsername(e.target.value)} - placeholder="아이디를 입력하세요" + value={email} // Changed value to email + onChange={(e) => setEmail(e.target.value)} // Changed onChange to setEmail + placeholder="이메일을 입력하세요" // Changed placeholder required />
diff --git a/src/components/ScrollToTop/ScrollToTop.jsx b/src/components/ScrollToTop/ScrollToTop.jsx new file mode 100644 index 0000000..276b090 --- /dev/null +++ b/src/components/ScrollToTop/ScrollToTop.jsx @@ -0,0 +1,25 @@ +import { useLayoutEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +export const ScrollToTop = () => { + const { pathname } = useLocation(); + + useLayoutEffect(() => { + const scrollToTop = () => { + // Attempt to scroll immediately with instant behavior + window.scrollTo({ top: 0, behavior: 'instant' }); + document.documentElement.scrollTo({ top: 0, behavior: 'instant' }); + document.body.scrollTo({ top: 0, behavior: 'instant' }); + }; + + scrollToTop(); // Run once immediately + + // Add a small timeout as a fallback for stubborn cases + // This ensures it runs after any potential immediate DOM updates + const timer = setTimeout(scrollToTop, 0); + + return () => clearTimeout(timer); // Clean up the timer + }, [pathname]); + + return null; +}; diff --git a/src/components/ScrollToTop/index.js b/src/components/ScrollToTop/index.js new file mode 100644 index 0000000..04f3393 --- /dev/null +++ b/src/components/ScrollToTop/index.js @@ -0,0 +1 @@ +export { ScrollToTop } from "./ScrollToTop"; diff --git a/src/components/SignupPopup/SignupPopup.jsx b/src/components/SignupPopup/SignupPopup.jsx index 6e93d5e..11b872a 100644 --- a/src/components/SignupPopup/SignupPopup.jsx +++ b/src/components/SignupPopup/SignupPopup.jsx @@ -29,9 +29,9 @@ export const SignupPopup = ({ onClose, onSwitchToLogin, onSwitchToPreferences }) // Simulating backend validation and storage with localStorage const mockUsers = JSON.parse(localStorage.getItem("registeredUsers") || "[]"); - // Check if username already exists - if (mockUsers.find((u) => u.username === username)) { - alert("이미 존재하는 아이디입니다."); + // Check if email already exists + if (mockUsers.find((u) => u.email === email)) { // Check for duplicate email + alert("이미 존재하는 이메일입니다."); return; } diff --git a/src/components/UnifiedHeader/UnifiedHeader.jsx b/src/components/UnifiedHeader/UnifiedHeader.jsx index c3173d2..f0fadc6 100644 --- a/src/components/UnifiedHeader/UnifiedHeader.jsx +++ b/src/components/UnifiedHeader/UnifiedHeader.jsx @@ -86,7 +86,7 @@ export const UnifiedHeader = () => { {isAuthenticated ? (
- {user?.username} + {user?.username}님 {/* Added "님" */} diff --git a/src/screens/AddIngredientPage/AddIngredientPage.jsx b/src/screens/AddIngredientPage/AddIngredientPage.jsx index 19f69ce..6d907fe 100644 --- a/src/screens/AddIngredientPage/AddIngredientPage.jsx +++ b/src/screens/AddIngredientPage/AddIngredientPage.jsx @@ -1,5 +1,5 @@ -import React, { useState } from "react"; -import { Link, useNavigate } from "react-router-dom"; +import React, { useState, useEffect } from "react"; +import { Link, useNavigate } from "react-router-dom"; // Removed useLocation import { UnifiedHeader } from "../../components/UnifiedHeader"; import "./style.css"; @@ -27,6 +27,10 @@ const MOCK_INGREDIENT_DATABASE = [ export const AddIngredientPage = () => { const navigate = useNavigate(); + // Removed useLocation and onAddMultipleIngredients here + // const location = useLocation(); + // const onAddMultipleIngredients = location.state?.onAddMultipleIngredients; + const [selectedCategory, setSelectedCategory] = useState("전체"); const [searchQuery, setSearchQuery] = useState(""); const [selectedIngredients, setSelectedIngredients] = useState([]); @@ -43,15 +47,26 @@ export const AddIngredientPage = () => { if (exists) { return prev.filter((item) => item.id !== ingredient.id); } else { - return [...prev, { ...ingredient, expiryDays: 7 }]; + return [...prev, { ...ingredient, expiryDays: 7 }]; // Default expiry days } }); }; const handleAddIngredients = () => { - // TODO: Send selected ingredients to backend - console.log("Adding ingredients:", selectedIngredients); - navigate("/desktop"); + if (selectedIngredients.length === 0) { + alert("추가할 식재료를 선택해주세요."); + return; + } + + // Directly update localStorage for userIngredients + const currentIngredients = JSON.parse(localStorage.getItem("userIngredients") || "[]"); + const updatedIngredients = [...currentIngredients, ...selectedIngredients]; + localStorage.setItem("userIngredients", JSON.stringify(updatedIngredients)); + + // TODO: Backend Integration: Send selectedIngredients to backend + console.log("Adding ingredients to mock DB:", selectedIngredients); + + navigate("/desktop"); // Navigate back to desktop }; return ( diff --git a/src/screens/CommunityContent/sections/Communitycontentpage/Communitycontentpage.jsx b/src/screens/CommunityContent/sections/Communitycontentpage/Communitycontentpage.jsx index c36c180..6773391 100644 --- a/src/screens/CommunityContent/sections/Communitycontentpage/Communitycontentpage.jsx +++ b/src/screens/CommunityContent/sections/Communitycontentpage/Communitycontentpage.jsx @@ -1,12 +1,11 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Link, useLocation, useParams } from "react-router-dom"; import { useAuth } from "../../../../contexts/AuthContext"; import { CommunitypageWrapper } from "../../../../components/CommunitypageWrapper"; -import { PageButton } from "../../../../components/PageButton"; import "./style.css"; -const ALL_POSTS = [ - // TODO: Backend Integration: Replace with API call to fetch all community posts +// Default posts for fallback if localStorage is empty +const DEFAULT_POSTS = [ { id: 1, title: "돼지고기 100g 나눔합니다.", author: "test3User", date: "2025-11-03", category: "나눔", content: "돼지고기를 너무 많이 샀네요 남는 돼지고기 나눔해요" }, { id: 2, title: "양파 2개 나눔해요", author: "user123", date: "2025-11-02", category: "나눔", content: "양파 2개 필요하신 분 가져가세요" }, { id: 3, title: "닭고기 500g 나눔", author: "foodlover", date: "2025-11-01", category: "나눔", content: "신선한 닭고기 나눔합니다" }, @@ -24,35 +23,46 @@ export const Communitycontentpage = () => { const { id } = useParams(); const { user, isAuthenticated } = useAuth(); const [newComment, setNewComment] = useState(""); - - // Load comments from localStorage for this specific post - const [comments, setComments] = useState(() => { + const [allCommunityPosts, setAllCommunityPosts] = useState([]); + + // Load comments from localStorage for this specific post, and re-load when 'id' changes + const [comments, setComments] = useState([]); + + useEffect(() => { const storedComments = localStorage.getItem(`comments_${id}`); - return storedComments ? JSON.parse(storedComments) : []; - }); - - const post = location.state?.post || { - // TODO: Backend Integration: Fetch post details by ID if not available in state - id: id, - title: "돼지고기 100g 나눔합니다.", - author: "test3User", - date: "2025-11-03", - category: "나눔", - content: "돼지고기를 너무 많이 샀네요 남는 돼지고기 나눔해요" - }; + setComments(storedComments ? JSON.parse(storedComments) : []); + }, [id]); // Dependency array includes 'id' + + useEffect(() => { + // Load all community posts from localStorage to find related posts + const storedPosts = JSON.parse(localStorage.getItem("communityPosts") || "[]"); + const combined = [...storedPosts, ...DEFAULT_POSTS]; + setAllCommunityPosts(combined); + }, []); // This effect runs only once on mount + + const post = location.state?.post || + allCommunityPosts.find(p => p.id === parseInt(id)) || // Try to find post from allCommunityPosts + { // Fallback if not found + id: id, + title: "게시글을 찾을 수 없습니다.", + author: "알 수 없음", + date: "", + category: "", + content: "해당 게시글을 불러오는 데 실패했습니다." + }; // Get related posts (2 before and 2 after current post) - const currentIndex = ALL_POSTS.findIndex(p => p.id === parseInt(id)); + const currentIndex = allCommunityPosts.findIndex(p => p.id === parseInt(id)); const relatedPosts = []; // Get 2 posts before for (let i = Math.max(0, currentIndex - 2); i < currentIndex; i++) { - if (ALL_POSTS[i]) relatedPosts.push(ALL_POSTS[i]); + if (allCommunityPosts[i]) relatedPosts.push(allCommunityPosts[i]); } // Get 2 posts after - for (let i = currentIndex + 1; i <= Math.min(ALL_POSTS.length - 1, currentIndex + 2); i++) { - if (ALL_POSTS[i]) relatedPosts.push(ALL_POSTS[i]); + for (let i = currentIndex + 1; i <= Math.min(allCommunityPosts.length - 1, currentIndex + 2); i++) { + if (allCommunityPosts[i]) relatedPosts.push(allCommunityPosts[i]); } const handleCommentSubmit = (e) => { diff --git a/src/screens/Desktop/Desktop.jsx b/src/screens/Desktop/Desktop.jsx index 0df0f3b..599f6c4 100644 --- a/src/screens/Desktop/Desktop.jsx +++ b/src/screens/Desktop/Desktop.jsx @@ -1,4 +1,5 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; +import { useLocation } from "react-router-dom"; // Import useLocation import { UnifiedHeader } from "../../components/UnifiedHeader"; import { Community } from "./sections/Community"; import { Footer } from "./sections/Footer"; @@ -6,7 +7,52 @@ import { IngredientInventory } from "./sections/IngredientInventory"; import { MenuInventory } from "./sections/MenuInventory"; import "./style.css"; +const INITIAL_INGREDIENTS = [ + { id: 1, name: "양파", category: "채소류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-1.png", expiryDays: 5 }, + { id: 2, name: "닭고기", category: "육류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 3 }, + { id: 3, name: "고추장", category: "가공류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 30 }, + { id: 4, name: "고춧가루", category: "가공류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 60 }, + { id: 5, name: "양배추", category: "채소류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 7 }, + { id: 6, name: "스테비아", category: "가공류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 90 }, + { id: 7, name: "마늘", category: "채소류", image: "https://c.animaapp.com/sjWITF5i/img/ingredientimage-7@2x.png", expiryDays: 14 }, +]; + export const Desktop = () => { + const [ingredients, setIngredients] = useState([]); + const location = useLocation(); // Initialize useLocation + + useEffect(() => { + // This effect will run on mount and when location changes, + // ensuring fresh data when returning from AddIngredientPage + const storedIngredients = JSON.parse(localStorage.getItem("userIngredients") || "[]"); + if (storedIngredients.length > 0) { + setIngredients(storedIngredients); + } else { + setIngredients(INITIAL_INGREDIENTS); + localStorage.setItem("userIngredients", JSON.stringify(INITIAL_INGREDIENTS)); + } + }, [location.pathname]); // Re-run when pathname changes + + const handleAddIngredient = (newIngredient) => { + setIngredients((prevIngredients) => { + const updatedIngredients = [...prevIngredients, newIngredient]; + localStorage.setItem("userIngredients", JSON.stringify(updatedIngredients)); + // TODO: Backend Integration: Send newIngredient to backend + console.log("Added ingredient to mock DB:", newIngredient); + return updatedIngredients; + }); + }; + + const handleAddMultipleIngredients = (newIngredients) => { + setIngredients((prevIngredients) => { + const updatedIngredients = [...prevIngredients, ...newIngredients]; + localStorage.setItem("userIngredients", JSON.stringify(updatedIngredients)); + // TODO: Backend Integration: Send newIngredients to backend + console.log("Added multiple ingredients to mock DB:", newIngredients); + return updatedIngredients; + }); + }; + return (
@@ -14,7 +60,11 @@ export const Desktop = () => {
오늘의 메뉴를 골라보세요
- +