$ npm i react-router-dom
main.jsx์์ ๋ผ์ฐํฐ ์ ์ฉํ๋ค.
BrowserRouter๊ฐ ๊ฐ์ธ๊ณ ์์ด์ผ ๋ผ์ฐํ
์ ์ฌ์ฉ ํ ์ ์๋ค.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root")).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
๋ง๋ค์ด๋์ ๋ชจ๋ ํ์ด์ง๋ฅผ ๋ผ์ฐํ
ํ๋ค.
import React from "react";
import { Route, Routes } from "react-router-dom";
import HomePage from "../Home/HomePage";
import ProductsPage from "../Products/ProductsPage";
import SingleProductPage from "../SingleProduct/SingleProductPage";
import SignupPage from "../Authentication/SignupPage";
import LoginPage from "../Authentication/LoginPage";
import CartPage from "../Cart/CartPage";
import MyOrderPage from "../MyOrderPage/MyOrderPage";
const Routing = () => {
return (
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/products" element={<ProductsPage />} />
<Route path="/product/:id" element={<SingleProductPage />} />
<Route path="/signup" element={<SignupPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/cart" element={<CartPage />} />
<Route path="/myorders" element={<MyOrderPage />} />
</Routes>
);
};
export default Routing;
App.jsx์์ routing ์ปดํฌ๋ํธ๋ฅผ ์ ์ฉํ๋ค.
import "./App.css";
import Navbar from "./components/Navbar/Navbar";
import Routing from "./components/Routing/Routing";
function App() {
return (
<div className="app">
<Navbar />
<main>
<Routing />
</main>
</div>
);
}
export default App;
navbar์์ link ์๋ง๊ฒ ์์ ํ๋ค.
<div className="align_center navbar_links">
<LinkWithIcon title="ํํ์ด์ง" link="." emoji={rocket} />
<LinkWithIcon title="์ํ๋ค" link="/products" emoji={star} />
<LinkWithIcon title="๋ก๊ทธ์ธ" link="/login" emoji={idButton} />
<LinkWithIcon title="๊ฐ์
" link="/signup" emoji={memo} />
<LinkWithIcon title="๋ด์ฃผ๋ฌธ" link="/myorders" emoji={order} />
<LinkWithIcon title="๋ก๊ทธ์์" link="." emoji={lock} />
<a href="/cart" className="align_center">
์ฅ๋ฐ๊ตฌ๋ <p className="align_center cart_counts">0</p>
</a>
</div>
์ ์ ์ฉ๋๋์ง ํ์ธํด๋ณธ๋ค.
๊ทธ๋ฆฌ๊ณ
๋ผ์ฐํฐ ์ฌ์ฉ ์์๋ aํ๊ทธ๋ฅผ ์ ์ฌ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์ Navlink๋ก ์์ ํ๋ค.
import { NavLink } from "react-router-dom";
import "./LinkWithIcon.css";
const LinkWithIcon = ({ title, link, emoji }) => {
return (
<NavLink to={link} className="align_center">
{title} <img src={emoji} alt="" className="link_emoji" />
</NavLink>
);
};
export default LinkWithIcon;
<NavLink to="/cart" className="align_center">
์ฅ๋ฐ๊ตฌ๋ <p className="align_center cart_counts">0</p>
</NavLink>
href -> to
/* ๋ค๋ธ ๋งํฌ๋ก ํด๋ฆญ ์ active ๋ค์ด๊ฐ */
.navbar_links > a.active {
font-weight: 600;
}
active ์ ๋งํฌ๊ฐ ๊ตต๊ฒ ํ์๋๋ค.
axios์ค์น ํ ๋ฐฑ์๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
utilsํด๋ ์์ฑ ํ
axios๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ jsํ์ผ์ ๋ง๋ ๋ค.
import axios from "axios";
//axios์ ๋ฏธ๋ฆฌ ๋ฐฑ์๋ ์๋ถ๋ถ ์ฃผ์๋ฅผ ์ ์ฅํ๋ค.
export default axios.create({
baseURL: "http://localhost:5000/api",
});
์์ฒญ์ ํ ๋ api ๋ท๋ถ๋ถ ๋ถํฐ ์์ฒญํ๋ฉด๋จ
default๋ก ์ค์ ์ ๊ฐ์ ธ์ฌ ๋ ์ด๋ฆ์ ๋ง์๋๋ก ์ค์ ์ด ๊ฐ๋ฅํจ
ProductsList.jsx
import apiClient from "../../utils/api-client";
const ProductsList = () => {
const [products, setProducts] = useState([]); // ์ํ๋ค
const [error, setError] = useState(""); //์๋ฌ
//์ฒ์ ์คํ ์ apiClient์ products์์ ๊ฐ์ ธ์จ๋ค
useEffect(() => {
apiClient
.get("/products") //base์ฃผ์ ๋ค์ ๋ถ์ฌ์ง
.then((res) => setProducts(res.data.products)) //์ฑ๊ณต ์ res ๋์ค๋ฉด products์ res์ data๋ฅผ ์ ์ฅํจ
.catch((err) => setError(err)); //์๋ฌ ๋ฐ์ ์ ์๋ฌ์ ์ ์ฅํ๋ค.
}, []);
์ ์ฅ ํ ํ์ธํ๋ค.
<section className="products_list_section">
<header className="align_center products_list_header">
<h2>์ํ๋ชฉ๋ก</h2>
<select name="sort" id="" className="products_sorting">
<option value="">์ ๋ ฌ๋ฐฉ๋ฒ</option>
<option value="price desc">๊ฐ๊ฒฉ๋์์</option>
<option value="price asc">๊ฐ๊ฒฉ๋ฎ์์</option>
<option value="rate desc">ํ์ ๋์์</option>
<option value="rate asc">ํ์ ๋ฎ์์</option>
</select>
</header>
<div className="products_list">
{/* ์๋ฌ๊ฐ ์์ ๊ฒฝ์ฐ ์๋ฌ ํ์ */}
{error && <em className="form_error">{error}</em>}
{/* products๊ฐ ์์ ๊ฒฝ์ฐ ๋ฐ๋ณต๋ฌธ์ผ๋ก ์ถ๋ ฅ */}
{products.map((product) => (
<ProductCard key={product._id} />
))}
</div>
db์ ๊ฐ์๋งํผ ์ถ๋ ฅ๋๋๊ฒ ํ์ธ์ด๋๋ฉด
ProductCard์ props๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํด์ ์ ๋ณด๋ฅผ ์ถ๋ ฅํ๋๋ก ํ๋ค.
ProductsList.jsx
<div className="products_list">
{/* ์๋ฌ๊ฐ ์์ ๊ฒฝ์ฐ ์๋ฌ ํ์ */}
{error && <em className="form_error">{error}</em>}
{/* products๊ฐ ์์ ๊ฒฝ์ฐ ๋ฐ๋ณต๋ฌธ์ผ๋ก ์ถ๋ ฅ */}
{products.map((product) => (
<ProductCard
key={product._id}
id={product._id}
title={product.title}
image={product.images[0]}
price={product.price}
rating={product.reviews.rate}
ratingCounts={product.reviews.counts}
stock={product.stock}
/>
))}
</div>
</div>
import "./ProductCard.css";
import star from "../../assets/white-star.png";
import basket from "../../assets/basket.png";
import { Link, NavLink } from "react-router-dom";
const ProductCard = ({
id,
image,
price,
title,
rating,
ratingCounts,
stock,
}) => {
return (
<article className="product_card">
<div className="product_image">
<Link href={`product/${id}`}>
<img
src={`http://localhost:5000/products/${image}`}
// {image}๋ง ๋ฃ์ผ๋ฉด ์ ๋๋ก ๋ถ๋ฌ์ค์ง ๋ชปํจ
// ๋ฐฑ์๋ ์๋ฒ ์ฃผ์๋ก ์์ฒญํ๋ฉด ๋จ
alt="product image"
/>
</Link>
</div>
<div className="product_details">
<h3 className="product_price">{price?.toLocaleString("ko-KR")} ์</h3>
{/* ?๋ฅผ ๋ฃ์ผ๋ฉด ๋ฐ์ดํฐ๊ฐ ์๋๋ผ๋ ์๋ฌ๊ฐ ๋์ง์์ */}
<p className="product_title">{title}</p>
<footer className="align_center product_info_footer">
<div className="align_center">
<p className="align_center product_rating">
<img src={star} alt="star" /> {rating}
</p>
<p className="product_review_count">{ratingCounts}</p>
</div>
{/* ์ฌ๊ณ ๊ฐ ์์ ๊ฒฝ์ฐ์๋ง ์ฅ๋ฐ๊ตฌ๋ ๋ด๊ธฐ ํ์ */}
{stock > 0 && (
<button className="add_to_cart">
<img src={basket} alt="basket button" />
</button>
)}
</footer>
</div>
</article>
);
};
export default ProductCard;
์ด๋ฏธ์ง์ ๊ฒฝ์ฐ ์ด๋ฐ์์ผ๋ก ์ง์ ์๋ฒ ์ฃผ์๋ก ์์ฒญํด์ผ ์ถ๋ ฅ๋๋ค.
ProductsSidebar.jsx์๋ ์์ ๋์ผํ๊ฒ ์นดํ
๊ณ ๋ฆฌ๋ฅผ ๋ฐฑ์๋์์ ๊ฐ์ ธ์จ๋ค.
import "./ProductsSidebar.css";
import LinkWithIcon from "../Navbar/LinkWithIcon";
import apiClient from "../../utils/api-client";
import { useEffect, useState } from "react";
const ProductsSidebar = () => {
const [categories, setCategories] = useState([]);
const [error, setError] = useState("");
// ์ฒ์ ์คํ ์ ๋ฐฑ์๋์์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
useEffect(() => {
apiClient
.get("/category") //category ์์
.then((res) => setCategories(res.data)) // ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ์ ์ฅ
.catch((err) => setError(err.message)); // ์๋ฌ๊ฐ ์์ผ๋ฉด ์ ์ฅ
}, []);
return (
<aside className="products_sidebar">
<h2>์นดํ
๊ณ ๋ฆฌ</h2>
<div className="category_links">
{error && <em className="form_error">{error}</em>}
{categories.map((category) => (
<LinkWithIcon
key={category._id}
title={category.name}
link={`products?category=${category.name}`}
emoji={`http://localhost:5000/category/${category.image}`}
sidebar={true}
/>
))}
</div>
</aside>
);
};
export default ProductsSidebar;