Skip to content
Closed
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
4 changes: 2 additions & 2 deletions .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Frontend CI (Vite Build)

on:
push:
branches: [ "main" ] # �귣ġ �̸��� master��� ���� �ʿ�
branches: [ "**" ] # �귣ġ �̸��� master��� ���� �ʿ�
pull_request:
branches: [ "main" ]
branches: [ "**" ]

jobs:
build-check:
Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -52,7 +53,9 @@ const router = createBrowserRouter([
export const App = () => {
return (
<AuthProvider>
<RouterProvider router={router} />
<RouterProvider router={router}>
<ScrollToTop /> {/* Render ScrollToTop inside RouterProvider */}
</RouterProvider>
</AuthProvider>
);
};
24 changes: 12 additions & 12 deletions src/components/LoginPopup/LoginPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,36 @@ 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();

const handleLogin = async (e) => {
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("이메일 또는 비밀번호가 일치하지 않습니다.");
}
};

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

<form className="login-popup-form" onSubmit={handleLogin}>
<div className="login-popup-input-group">
<label className="login-popup-label">아이디</label>
<label className="login-popup-label">이메일</label> {/* Changed label */}
<input
type="text"
type="email" // Changed type to email
className="login-popup-input"
value={username}
onChange={(e) => 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
/>
</div>
Expand Down
25 changes: 25 additions & 0 deletions src/components/ScrollToTop/ScrollToTop.jsx
Original file line number Diff line number Diff line change
@@ -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;
};
1 change: 1 addition & 0 deletions src/components/ScrollToTop/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ScrollToTop } from "./ScrollToTop";
6 changes: 3 additions & 3 deletions src/components/SignupPopup/SignupPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/UnifiedHeader/UnifiedHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const UnifiedHeader = () => {
</Link>
{isAuthenticated ? (
<div className="unified-user-info">
<span className="unified-username">{user?.username}</span>
<span className="unified-username">{user?.username}</span> {/* Added "님" */}
<button className="unified-logout-btn" onClick={logout}>
로그아웃
</button>
Expand Down
27 changes: 21 additions & 6 deletions src/screens/AddIngredientPage/AddIngredientPage.jsx
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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([]);
Expand All @@ -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 (
Expand Down
Original file line number Diff line number Diff line change
@@ -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: "신선한 닭고기 나눔합니다" },
Expand All @@ -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) => {
Expand Down
54 changes: 52 additions & 2 deletions src/screens/Desktop/Desktop.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,70 @@
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";
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 (
<div className="desktop" data-model-id="45:2">
<UnifiedHeader />
<div className="site-header-name">
<div className="choose-your-receipt">오늘의 메뉴를 골라보세요</div>
</div>

<IngredientInventory />
<IngredientInventory
ingredients={ingredients}
onAddIngredient={handleAddIngredient}
onAddMultipleIngredients={handleAddMultipleIngredients}
/>
<MenuInventory />
<Community />
<Footer />
Expand Down
Loading