Skip to content
Open
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
286 changes: 200 additions & 86 deletions README.md

Large diffs are not rendered by default.

36 changes: 33 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"styled-components": "^6.4.2"
"styled-components": "^6.4.2",
"zustand": "^5.0.14"
},
"devDependencies": {
"@types/react": "^18.2.66",
Expand Down
18 changes: 14 additions & 4 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
import "./App.css";
import { useState } from "react";
import { useEffect, useState } from "react";
import Header from "./components/Header/Header";
import CategoryFilter from "./components/CategoryFilter/CategoryFilter";
import RestaurantList from "./components/RestaurantList/RestaurantList";
import RestaurantDetailModal from "./components/Modal/RestaurantDetailModal";
import AddRestaurantModal from "./components/Modal/AddRestaurantModal";
import { ALL_CATEGORY } from "./constants/categories";
import useRestaurantStore from "./store/useRestaurantStore";
import useFilterStore from "./store/useFilterStore";

export default function App() {
const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORY);
const [clickedRestaurant, setClickedRestaurant] = useState(null);
const [isAddModalOpen, setIsAddModalOpen] = useState(false);

const selectedCategory = useFilterStore((state) => state.selectedCategory);
const setSelectedCategory = useFilterStore(
(state) => state.setSelectedCategory,
);
const handleCategoryChange = (e) => setSelectedCategory(e.target.value);
const handleRestaurantClick = (restaurant) => setClickedRestaurant(restaurant);
const handleRestaurantClick = (restaurant) =>
setClickedRestaurant(restaurant);
const handleDetailModalClose = () => setClickedRestaurant(null);
const handleAddModalOpen = () => setIsAddModalOpen(true);
const handleAddModalClose = () => setIsAddModalOpen(false);

const fetchRestaurants = useRestaurantStore((state) => state.fetchRestaurants);
useEffect(() => {
fetchRestaurants();
}, [fetchRestaurants]);

return (
<>
<Header onAddModalOpen={handleAddModalOpen} />
Expand Down
11 changes: 8 additions & 3 deletions src/components/Modal/AddRestaurantModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ import { useState } from "react";
import Modal from "./Modal";
import styled from "styled-components";
import { textCaption } from "../../styles/typography";
import { useRestaurantContext } from "../../context/useRestaurantContext";
import useRestaurantStore from "../../store/useRestaurantStore";

export default function AddRestaurantModal({ onClose }) {
const { registerRestaurant } = useRestaurantContext();
const registerRestaurant = useRestaurantStore((state) => state.registerRestaurant);
const [category, setCategory] = useState("");
const [name, setName] = useState("");
const [description, setDescription] = useState("");

const handleSubmit = async (e) => {
e.preventDefault();
try {
await registerRestaurant({ id: crypto.randomUUID(), category, name, description });
await registerRestaurant({
id: crypto.randomUUID(),
category,
name,
description,
});
onClose();
} catch {
alert("음식점 추가에 실패했습니다. 다시 시도해주세요.");
Expand Down
12 changes: 9 additions & 3 deletions src/components/RestaurantList/RestaurantList.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { CATEGORY_IMAGES } from "../../constants/categoryImages";
import styled from "styled-components";
import { textSubtitle, textBody } from "../../styles/typography";
import { useRestaurantContext } from "../../context/useRestaurantContext";
import useRestaurantStore from "../../store/useRestaurantStore";
import { ALL_CATEGORY } from "../../constants/categories";

export default function RestaurantList({ selectedCategory, onRestaurantClick }) {
const { newRestaurants, isLoading, error } = useRestaurantContext();
export default function RestaurantList({
selectedCategory,
onRestaurantClick,
}) {
const newRestaurants = useRestaurantStore((state) => state.newRestaurants);
const isLoading = useRestaurantStore((state) => state.isLoading);
const error = useRestaurantStore((state) => state.error);

const filteredRestaurants =
selectedCategory === ALL_CATEGORY
? newRestaurants
Expand Down
22 changes: 0 additions & 22 deletions src/context/RestaurantContext.jsx

This file was deleted.

14 changes: 0 additions & 14 deletions src/context/useRestaurantContext.js

This file was deleted.

31 changes: 0 additions & 31 deletions src/hooks/useRestaurants.js

This file was deleted.

7 changes: 2 additions & 5 deletions src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import { RestaurantProvider } from "./context/RestaurantContext";

ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RestaurantProvider>
<App />
</RestaurantProvider>
</React.StrictMode>
<App />
</React.StrictMode>,
);
18 changes: 18 additions & 0 deletions src/store/useFilterStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
import { ALL_CATEGORY } from "../constants/categories";

const useFilterStore = create(
persist(
(set) => ({
selectedCategory: ALL_CATEGORY,
setSelectedCategory: (category) => set({ selectedCategory: category }),
}),
{
name: "self-paced-react-category",
storage: createJSONStorage(() => sessionStorage),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[배움]
저는 localStorage를 사용했는데 카테고리 필터의 경우에는 지금 내가 보고 있는 화면의 상태로 다음에 앱을 열면 전체 목록부터 다시 보는 것이 자연스러운 탐색 상태라고 생각되네요! 이 점에서 sessionStorage가 더 적절한 것 같습니다.

},
),
);

export default useFilterStore;
27 changes: 27 additions & 0 deletions src/store/useRestaurantStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { create } from "zustand";
import { getRestaurants, addRestaurant } from "../api";

const useRestaurantStore = create((set, get) => ({
newRestaurants: [],
error: null,
isLoading: false,

fetchRestaurants: async () => {
set({ isLoading: true, error: null });
try {
const data = await getRestaurants();
set({ newRestaurants: data });
} catch {
set({ error: "음식점 목록을 불러오지 못했습니다." });
} finally {
set({ isLoading: false });
}
},

registerRestaurant: async (newRestaurant) => {
await addRestaurant(newRestaurant);
await get().fetchRestaurants();
},
}));

export default useRestaurantStore;