diff --git a/package-lock.json b/package-lock.json
index 957c9d31..b37997c1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "axios": "^1.7.9",
"firebase": "^11.1.0",
"lodash.throttle": "^4.1.1",
"react": "^18.2.0",
@@ -5900,6 +5901,29 @@
"node": ">=4"
}
},
+ "node_modules/axios": {
+ "version": "1.7.9",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
+ "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/axios/node_modules/form-data": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
+ "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -15179,6 +15203,11 @@
"node": ">= 0.10"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
diff --git a/package.json b/package.json
index 5f5b0504..3fbc0b7a 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
+ "axios": "^1.7.9",
"firebase": "^11.1.0",
"lodash.throttle": "^4.1.1",
"react": "^18.2.0",
diff --git a/src/Hooks/useAutoClose.jsx b/src/Hooks/useAutoClose.jsx
new file mode 100644
index 00000000..6041a98e
--- /dev/null
+++ b/src/Hooks/useAutoClose.jsx
@@ -0,0 +1,21 @@
+import { useState, useEffect, useRef } from "react";
+
+export function useAutoClose(initialState) {
+ const [isOpen, setIsOpen] = useState(initialState);
+ const ref = useRef(null);
+
+ const handleOutsideClick = (e) => {
+ if (ref.current && !ref.current.contains(e?.target)) {
+ setIsOpen(false);
+ }
+ };
+
+ useEffect(() => {
+ document.addEventListener("click", handleOutsideClick, true);
+ return () => {
+ document.removeEventListener("click", handleOutsideClick, true);
+ };
+ }, []);
+
+ return { ref, isOpen, setIsOpen };
+}
diff --git a/src/Hooks/useFormatting.jsx b/src/Hooks/useFormatting.jsx
new file mode 100644
index 00000000..335e24c2
--- /dev/null
+++ b/src/Hooks/useFormatting.jsx
@@ -0,0 +1,33 @@
+export function useFormatDate(data) {
+ const date = new Date(data);
+ const formattedDate = `${date.getFullYear()}.${String(
+ date.getMonth() + 1
+ ).padStart(2, "0")}.${String(date.getDate()).padStart(2, "0")}`;
+ return formattedDate;
+}
+
+export const useFormatPrice = (data, currency = "KRW") => {
+ if (typeof data !== "number") return "가격 정보 없음";
+ return data.toLocaleString("ko-KR", {
+ style: "decimal",
+ currency: currency,
+ });
+};
+
+export const useFormatUpDate = (timestamp) => {
+ const now = new Date();
+ const past = new Date(timestamp);
+ const diff = (now - past) / 1000;
+
+ if (diff < 60) {
+ return "1분전";
+ } else if (diff < 3600) {
+ return `${Math.floor(diff / 60)}분 전`; // 1시간 미만
+ } else if (diff < 86400) {
+ return `${Math.floor(diff / 3600)}시간 전`; // 24시간 미만
+ } else if (diff < 30 * 86400) {
+ return `${Math.floor(diff / 86400)}일 전`; // 30일 미만
+ } else {
+ return past.toISOString().split("T")[0]; // YYYY-MM-DD 형식 (한 달 이상 지난 경우)
+ }
+};
diff --git a/src/Main.jsx b/src/Main.jsx
new file mode 100644
index 00000000..7ac1c86c
--- /dev/null
+++ b/src/Main.jsx
@@ -0,0 +1,70 @@
+import { Route, BrowserRouter, Routes } from "react-router-dom";
+import { createGlobalStyle } from "styled-components";
+//
+import LandingPage from "./pages/LandingPage/LandingPage.jsx";
+import App from "./App.js";
+import HomePage from "./pages/HomePage/HomePage.jsx";
+import AddItem from "./pages/AddItem/AddItem.jsx";
+import Product from "./pages/ProductPage/Product.jsx";
+import Test from "./components/TestPage.jsx";
+//
+//
+const GlobalStyle = createGlobalStyle`
+ * {
+ box-sizing: border-box;
+ }
+ body {
+ font-family: 'Pretendard', sans-serif;
+ font-display: swap;
+ margin: 0;
+ padding: 0;
+ }
+ html {
+ margin: 0;
+ padding: 0;
+ }
+ a {
+ text-decoration: none;
+ color: #ffffff;
+ }
+ p{
+ margin: 0px;
+ }
+ @font-face {
+ font-family: 'Pretendard';
+ src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff2') format('woff2');
+ src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff');
+ font-display: swap;
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+font-family: "Pretendard";
+src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Bold.woff2') format('woff2');
+src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Bold.woff') format('woff');
+ font-display: swap;
+ font-weight: 600;
+ font-style: normal;
+}
+`;
+//
+function Main() {
+ return (
+ <>
+
+
+
+ } />
+ }>
+ } />
+ } />
+ } />
+ } />
+
+
+
+ >
+ );
+}
+export default Main;
diff --git a/src/api/comment.api.jsx b/src/api/comment.api.jsx
new file mode 100644
index 00000000..2c861071
--- /dev/null
+++ b/src/api/comment.api.jsx
@@ -0,0 +1,17 @@
+import axios from "axios";
+const BASE_URL = "https://panda-market-api.vercel.app";
+
+export async function getProductComments(productId, limit = 3) {
+ try {
+ const res = await axios.get(
+ `${BASE_URL}/products/${productId}/comments?limit=${limit}`
+ );
+ if (!res) {
+ throw new Error("리뷰 불러오기 실패");
+ }
+ return res.data;
+ } catch (error) {
+ console.error(error);
+ return null;
+ }
+}
diff --git a/src/api.js b/src/api/product.api.jsx
similarity index 74%
rename from src/api.js
rename to src/api/product.api.jsx
index e8001b33..0fc94123 100644
--- a/src/api.js
+++ b/src/api/product.api.jsx
@@ -1,3 +1,4 @@
+import axios from "axios";
const BASE_URL = "https://panda-market-api.vercel.app";
export async function getProducts({
@@ -28,3 +29,14 @@ export async function bestProducts({ device }) {
const body = await response.json();
return body;
}
+
+export async function getProductInfo(productId) {
+ try {
+ const response = await axios.get(`${BASE_URL}/products/${productId}`);
+ if (!response) throw new Error("제품정보 get api 실패");
+ return response.data;
+ } catch (error) {
+ console.error(error, "제품정보 api 실패");
+ return null;
+ }
+}
diff --git a/src/assets/btn_sort.svg b/src/assets/btn_sort.svg
deleted file mode 100644
index 41751e7b..00000000
--- a/src/assets/btn_sort.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/src/assets/favoriteLogo.svg b/src/assets/favoriteLogo.svg
deleted file mode 100644
index 282d802a..00000000
--- a/src/assets/favoriteLogo.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/src/assets/icons/active.heart.icon.svg b/src/assets/icons/active.heart.icon.svg
new file mode 100644
index 00000000..a2290cd2
--- /dev/null
+++ b/src/assets/icons/active.heart.icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/myLogo.svg b/src/assets/icons/default.profile.icon.svg
similarity index 100%
rename from src/assets/icons/myLogo.svg
rename to src/assets/icons/default.profile.icon.svg
diff --git a/src/assets/icons/inactive.heart.icon.svg b/src/assets/icons/inactive.heart.icon.svg
new file mode 100644
index 00000000..e7e9f3ed
--- /dev/null
+++ b/src/assets/icons/inactive.heart.icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/kebab.icon.svg b/src/assets/icons/kebab.icon.svg
new file mode 100644
index 00000000..dd7ed7f5
--- /dev/null
+++ b/src/assets/icons/kebab.icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/icons/return.icon.svg b/src/assets/icons/return.icon.svg
new file mode 100644
index 00000000..a8265375
--- /dev/null
+++ b/src/assets/icons/return.icon.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/select.icon.svg b/src/assets/icons/select.icon.svg
new file mode 100644
index 00000000..358e4b87
--- /dev/null
+++ b/src/assets/icons/select.icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/no-comments.svg b/src/assets/no-comments.svg
new file mode 100644
index 00000000..5444cbbb
--- /dev/null
+++ b/src/assets/no-comments.svg
@@ -0,0 +1,17 @@
+
diff --git a/src/components/CommentCard/CommentCard.jsx b/src/components/CommentCard/CommentCard.jsx
new file mode 100644
index 00000000..486ad262
--- /dev/null
+++ b/src/components/CommentCard/CommentCard.jsx
@@ -0,0 +1,71 @@
+import { useEffect, useState } from "react";
+//
+import * as S from "./CommentCard.style.jsx";
+import defaultImg from "../../assets/icons/default.profile.icon.svg";
+import { Input } from "../common/Input/Input.jsx";
+import { EditSelect } from "../common/Select/Select.jsx";
+import { button } from "../../constants/globalConstant.jsx";
+import { useFormatUpDate } from "../../hooks/useFormatting.jsx";
+//
+export default function CommentCard({ data }) {
+ const [initialValue, setInitialValue] = useState(data.content);
+ const [isEditing, setIsEditing] = useState(null);
+ const [isDelete, setIsDelete] = useState(null);
+ const formattedUpdate = useFormatUpDate(data.updatedAt);
+ //
+ const handleOnChange = (option) => {
+ if (option === button.edit) {
+ setIsEditing(data.id);
+ }
+ if (option === button.delete) {
+ setIsDelete(data.id);
+ }
+ return;
+ };
+
+ useEffect(() => {
+ //삭제 리퀘스트 예정
+ }, [isDelete]);
+ //
+ return (
+
+
+ {data.id === isEditing ? (
+ setInitialValue(target.value)}
+ />
+ ) : (
+ {initialValue}
+ )}
+
+
+
+ {data.writer.image ? (
+
+ ) : (
+
+ )}
+
+
+ {data.writer.nickname}
+ {formattedUpdate}
+
+
+ {data.id === isEditing && (
+
+ setIsEditing("")}>
+ {button.cancel}
+
+ setIsEditing("")}>
+ {button.editConfirm}
+
+
+ )}
+
+
+ {data.id !== isEditing && }
+
+ );
+}
diff --git a/src/components/CommentCard/CommentCard.style.jsx b/src/components/CommentCard/CommentCard.style.jsx
new file mode 100644
index 00000000..ab3dfd39
--- /dev/null
+++ b/src/components/CommentCard/CommentCard.style.jsx
@@ -0,0 +1,68 @@
+import styled from "styled-components";
+import theme from "../../style/theme";
+
+export const CommentWrapper = styled.div`
+ width: 100%;
+ height: auto;
+ display: flex;
+ justify-content: space-between;
+ border-bottom: 1px solid ${theme.color.gray200};
+ padding-bottom: 12px;
+`;
+export const CommentFlex = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+`;
+export const Content = styled.p`
+ font: ${theme.font.H7Regular};
+`;
+export const ProfileWrapper = styled.div`
+ display: flex;
+ justify-content: space-between;
+ gap: 8px;
+`;
+export const ProfileDateWrapper = styled.div`
+ display: flex;
+ gap: 8px;
+`;
+
+export const ProfileImg = styled.img`
+ width: 32px;
+ height: 32px;
+ border-radius: 99px;
+ border: none;
+`;
+export const NickNameDateWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+`;
+
+export const NickName = styled.p`
+ font: ${theme.font.H8};
+`;
+export const Date = styled.p`
+ font: ${theme.font.H8};
+ color: ${theme.color.gray400};
+`;
+
+export const CancelBtn = styled.button`
+ width: 68px;
+ height: 47px;
+ font: ${theme.font.H5Bold};
+ border: none;
+ background-color: ${theme.color.white};
+ color: ${theme.color.gray500};
+`;
+
+export const EditConfirmBtn = styled.button`
+ width: 106px;
+ height: 42px;
+ border-radius: 8px;
+ font: ${theme.font.H5Bold};
+ border: none;
+ background-color: ${theme.color.blue};
+ color: ${theme.color.white};
+`;
diff --git a/src/components/Main.js b/src/components/Main.js
deleted file mode 100644
index c04bf732..00000000
--- a/src/components/Main.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Route, BrowserRouter, Routes } from "react-router-dom";
-import { createGlobalStyle } from "styled-components";
-//
-import LandingPage from "../pages/LandingPage/LandingPage.jsx";
-import App from "../App";
-import HomePage from "../pages/HomePage";
-import AddItem from "../pages/AddItem/AddItem.jsx";
-//
-import useWindowSize from "../hooks/useWindowSize";
-//
-const GlobalStyle = createGlobalStyle`
- * {
- box-sizing: border-box;
- }
- body {
- font-family: 'Pretendard', sans-serif;
- font-display: swap;
- margin: 0;
- padding: 0;
- }
- html {
- margin: 0;
- padding: 0;
- }
- a {
- text-decoration: none;
- color: #ffffff;
- }
-`;
-//
-function Main() {
- const deviceType = useWindowSize();
- return (
- <>
-
-
-
- } />
- }>
- } />
- } />
-
-
-
- >
- );
-}
-export default Main;
diff --git a/src/components/SelectBox.js b/src/components/SelectBox.js
deleted file mode 100644
index ab586123..00000000
--- a/src/components/SelectBox.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import Select from "react-select";
-import SelectImg from "../assets/btn_sort.svg";
-import useWindowSize from "../hooks/useWindowSize";
-import styled from "styled-components";
-
-//
-const CustomSelectWrapper = styled.div`
- width: ${({ device }) => (device !== "mobile" ? "130px" : "42px")};
- height: 42px;
- order: ${({ device }) => (device === "mobile" ? 3 : 4)};
- padding: "0px 0px";
- border-radius: 12px;
-`;
-const customStyles = () => ({
- control: (styles) => ({
- ...styles,
- padding: "0px 0px",
- width: `${({ device }) => (device !== "mobile" ? "130px" : "42px")}`,
- backgroundColor: "#ffffff",
- border: "none",
- }),
- valueContainer: (styles) => ({
- ...styles,
- width: "100%",
- padding: "0px 0px",
- }),
- indicatorsContainer: (styles) => ({
- ...styles,
- display: "none",
- }),
- menu: (styles) => ({
- ...styles,
- borderRadius: "14px",
- padding: "0px 0px",
- }),
- option: (styles) => ({
- ...styles,
- backgroundColor: "#ffffff",
- color: "#1F2937",
- width: "130px",
- padding: "0px 0px",
- }),
- placeholder: (styles) => ({
- ...styles,
- textAlign: "center",
- padding: "0px 0px",
- backgroundImage: `url(${SelectImg}) no-repeat center/cover`,
- }),
-});
-
-const options = [
- { value: "recent", label: "최신순" },
-
- { value: "favorite", label: "좋아요" },
-];
-function SelectBox({ value, onChange }) {
- const device = useWindowSize();
-
- return (
-
-
-
- );
-}
-export default SelectBox;
diff --git a/src/components/Tag/Tag.jsx b/src/components/Tag/Tag.jsx
index fb4f9f2d..f84891cf 100644
--- a/src/components/Tag/Tag.jsx
+++ b/src/components/Tag/Tag.jsx
@@ -1,15 +1,27 @@
import DeleteButton from "../../assets/icons/DeleteIcon.svg";
import * as S from "./Tag.style";
-export default function Tag({ value, onClick }) {
- const handleClickDeleteBtm = () => {
- onClick(value);
- };
+export default function Tag({ tags, ...props }) {
+ const { onClick, ...rest } = props;
+ //...rest : $product(gap8px,items/id페이지)
return (
-
-
- #{value}
-
-
-
+
+ {tags?.length > 0 &&
+ tags.map((tag) => {
+ return (
+
+
+ #{tag}
+ {!rest.$product && (
+ onClick(tag)}
+ src={DeleteButton}
+ />
+ )}
+
+
+ );
+ })}
+
);
}
diff --git a/src/components/Tag/Tag.style.jsx b/src/components/Tag/Tag.style.jsx
index 2ad5d95a..2c24e7bf 100644
--- a/src/components/Tag/Tag.style.jsx
+++ b/src/components/Tag/Tag.style.jsx
@@ -1,6 +1,17 @@
-import styled from "styled-components";
+import styled, { css } from "styled-components";
import theme from "../../style/theme";
+export const TagsContainer = styled.div`
+ width: 100%;
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ ${(props) =>
+ props.$product &&
+ css`
+ gap: 8px;
+ `}
+`;
export const Container = styled.div`
background-color: ${theme.color.gray100};
width: auto;
@@ -22,8 +33,12 @@ export const Tag = styled.div`
font-weight: 400;
font-size: 16px;
line-height: 26px;
+ @media (max-width: 375px) {
+ }
+ @media (min-width: 376px) and (max-width: 768px) {
+ }
`;
export const DeleteButton = styled.img`
width: 22px;
- height: 24px;
+ height: 22px;
`;
diff --git a/src/components/TestPage.jsx b/src/components/TestPage.jsx
new file mode 100644
index 00000000..e6a3ccb4
--- /dev/null
+++ b/src/components/TestPage.jsx
@@ -0,0 +1,19 @@
+/// 컴포넌트 테스트 페이지 완료후 삭제 예정
+
+// import Button from "./common/Button/Button";
+// import BtnHeart from "./common/BtnHeart/BtnHeart";
+// import Tag from "../components/Tag/Tag";
+// import { Input } from "./common/Input/Input";
+export default function TestPage() {
+ // //태그 테스트 배열
+ // const tag3 = [];
+ // const tag1 = ["태그1", "태그2"];
+ // const tags = ["태그1", "태그2", "태그3", "태그4"];
+ return (
+ <>
+ {/*
+
+ */}
+ >
+ );
+}
diff --git a/src/components/common/BtnHeart/BtnHeart.jsx b/src/components/common/BtnHeart/BtnHeart.jsx
new file mode 100644
index 00000000..4ee40f3e
--- /dev/null
+++ b/src/components/common/BtnHeart/BtnHeart.jsx
@@ -0,0 +1,36 @@
+import activeHeart from "../../../assets/icons/active.heart.icon.svg";
+import inactiveHeart from "../../../assets/icons/inactive.heart.icon.svg";
+//
+import * as S from "./BtnHeart.style";
+//
+export default function BtnHeart({ value, ...props }) {
+ const { border, active, onClick, small, ...rest } = props;
+ const onClickChange = (e) => {
+ onClick(e);
+ };
+ return (
+ <>
+ {active ? (
+
+
+ {value}
+
+ ) : (
+
+
+ {value}
+
+ )}
+ >
+ );
+}
diff --git a/src/components/common/BtnHeart/BtnHeart.style.jsx b/src/components/common/BtnHeart/BtnHeart.style.jsx
new file mode 100644
index 00000000..78db533a
--- /dev/null
+++ b/src/components/common/BtnHeart/BtnHeart.style.jsx
@@ -0,0 +1,70 @@
+import styled, { css } from "styled-components";
+import theme from "../../../style/theme";
+//
+
+export const InactiveBtnHeart = styled.button`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ width: 87px;
+ height: 40px;
+ border: none;
+ border-radius: 35px;
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 26px;
+
+ background: ${theme.color.white};
+ color: ${theme.color.gray500};
+ ${(props) =>
+ props.$small &&
+ css`
+ width: 79px;
+ height: 32px;
+ `};
+ ${(props) =>
+ props.$border &&
+ css`
+ border: 1px solid ${theme.color.gray200};
+ `};
+`;
+
+export const ActiveBtnHeart = styled.button`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ width: 87px;
+ height: 40px;
+ border: none;
+ border-radius: 35px;
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 26px;
+ background: ${theme.color.white};
+ color: ${theme.color.gray500};
+ ${(props) =>
+ props.$small &&
+ css`
+ width: 79px;
+ height: 32px;
+ `};
+ ${(props) =>
+ props.$border &&
+ css`
+ border: 1px solid ${theme.color.gray200};
+ `};
+`;
+
+export const HeartImg = styled.img`
+ width: 26px;
+ height: 23px;
+
+ ${(props) =>
+ props.$small &&
+ css`
+ width: 20px;
+ height: 17px;
+ `}
+`;
diff --git a/src/components/common/Button/Button.jsx b/src/components/common/Button/Button.jsx
index 1eb339a0..17e28380 100644
--- a/src/components/common/Button/Button.jsx
+++ b/src/components/common/Button/Button.jsx
@@ -1,10 +1,39 @@
import * as S from "./Button.style";
//
//버튼 컨테이너 필요
-export default function Button({ children, disabled, ...props }) {
+export default function Button({ onClick, ...props }) {
+ const {
+ $small,
+ medium,
+ value,
+ toggle,
+ square,
+ circle,
+ heart,
+ children,
+ disabled,
+ ...rest
+ } = props;
+ //...rest: $square,$small,$medium,toggle,$circle,children,disabled
+ const onClickChange = (e) => {
+ onClick(e);
+ };
return (
-
- {children}
-
+ <>
+ {toggle ? (
+
+ ) : (
+
+ {children}
+
+ )}
+ >
);
}
diff --git a/src/components/common/Button/Button.style.jsx b/src/components/common/Button/Button.style.jsx
index 037b062a..629b2b82 100644
--- a/src/components/common/Button/Button.style.jsx
+++ b/src/components/common/Button/Button.style.jsx
@@ -1,6 +1,7 @@
-import styled from "styled-components";
+import styled, { css } from "styled-components";
import theme from "../../../style/theme";
//
+
export const Button = styled.button`
display: flex;
justify-content: center;
@@ -18,4 +19,16 @@ export const Button = styled.button`
&:disabled {
background-color: ${theme.color.gray400};
}
+
+ ${(props) =>
+ props.$medium &&
+ css`
+ width: 240px;
+ height: 48px;
+ `};
+ ${(props) =>
+ props.$circle &&
+ css`
+ border-radius: 40px;
+ `};
`;
diff --git a/src/components/common/Input/Input.jsx b/src/components/common/Input/Input.jsx
index fd027331..8b41a0c7 100644
--- a/src/components/common/Input/Input.jsx
+++ b/src/components/common/Input/Input.jsx
@@ -3,36 +3,22 @@ import DeleteIcon from "../../../assets/icons/DeleteIcon.svg";
import * as S from "./Input.style";
import { useRef, useState } from "react";
//
-export function Input({ label, placeholder, name, onChange, ...props }) {
- const { tag, onKeyUp, value, type, textArea, ...rest } = props;
- const handleChange = (e) => {
- onChange(e.target);
- };
- const handleOnKeyUp = (e) => {
- onKeyUp(e);
- };
- const handleChangeTag = (e) => {
- onChange(e.target.value);
- };
+export function Input({ onChange, ...props }) {
+ const { label, tag, onKeyUp, ...rest } = props;
+
return (
- {label}
+ {!label && {label}}
);
}
//
-export function ImgInput({ placeholder, type, name, onChange, ...props }) {
- const { ...rest } = props;
+export function ImgInput({ onChange, ...props }) {
const imgRef = useRef();
const [imgPreview, setImgPreview] = useState("");
@@ -55,16 +41,15 @@ export function ImgInput({ placeholder, type, name, onChange, ...props }) {
- {placeholder}
+ {props.placeholder}
diff --git a/src/components/common/Input/Input.style.jsx b/src/components/common/Input/Input.style.jsx
index 062b9c9b..a7996ccd 100644
--- a/src/components/common/Input/Input.style.jsx
+++ b/src/components/common/Input/Input.style.jsx
@@ -17,6 +17,12 @@ export const Label = styled.label`
color: ${theme.color.gray800};
font-weight: 700;
line-height: 26px;
+ ${(props) =>
+ props.$comment &&
+ css`
+ font-weight: 600;
+ font-size: 16px;
+ `}
`;
export const Input = styled.input`
@@ -37,6 +43,9 @@ export const Input = styled.input`
font-size: 16px;
text-align: left;
position: absolute;
+ white-space: normal;
+ word-break: break-word;
+
top: 16px;
left: 24px;
}
@@ -48,6 +57,22 @@ export const Input = styled.input`
css`
height: 282px;
`}
+ ${(props) =>
+ props.$comment &&
+ css`
+ font: ${theme.font.H7Regular};
+ height: 104px;
+ @media (max-width: 375px) {
+ height: 129px;
+ }
+ `}
+
+ ${(props) =>
+ props.$edit &&
+ css`
+ font: ${theme.font.H7Regular};
+ height: 80px;
+ `}
`;
//imgInput 부분
diff --git a/src/components/common/Layout/Nav.jsx b/src/components/common/Layout/Nav.jsx
index f67835ac..8b85571d 100644
--- a/src/components/common/Layout/Nav.jsx
+++ b/src/components/common/Layout/Nav.jsx
@@ -1,7 +1,7 @@
import { Link, NavLink } from "react-router-dom";
import pandaLogo from "../../../assets/Logo/pandaLogo.svg";
import textLogo from "../../../assets/Logo/textLogo.svg";
-import myLogo from "../../../assets/icons/myLogo.svg";
+import myLogo from "../../../assets/icons/default.profile.icon.svg";
import * as S from "./Nav.style.jsx";
import useWindowSize from "../../../hooks/useWindowSize";
diff --git a/src/components/common/Select/Select.jsx b/src/components/common/Select/Select.jsx
new file mode 100644
index 00000000..b82a7e43
--- /dev/null
+++ b/src/components/common/Select/Select.jsx
@@ -0,0 +1,72 @@
+import React, { useState } from "react";
+import * as S from "./Select.style";
+import selectIcon from "../../../assets/icons/select.icon.svg";
+import kebabIcon from "../../../assets/icons/kebab.icon.svg";
+
+import { useAutoClose } from "../../../hooks/useAutoClose";
+import { button } from "../../../constants/globalConstant";
+//
+
+export function SortSelect({ onChange, ...props }) {
+ const options = ["최신순", "좋아요순"];
+
+ const [selected, setSelected] = useState(options[0] | "options배열 필요");
+ const { ref, isOpen, setIsOpen } = useAutoClose(false);
+
+ const handleOptionClick = (option) => {
+ setSelected(option);
+ setIsOpen(false);
+ onChange(option);
+ };
+
+ return (
+ <>
+
+ setIsOpen(!isOpen)}>
+ {selected}
+
+
+
+ {options.map((option) => (
+ handleOptionClick(option)}
+ >
+ {option}
+
+ ))}
+
+
+ >
+ );
+}
+
+export function EditSelect({ onChange, ...props }) {
+ const options = [button.edit, button.delete];
+ const { ref, isOpen, setIsOpen } = useAutoClose(false);
+
+ const handleOptionClick = (option) => {
+ setIsOpen(false);
+ onChange(option);
+ };
+
+ return (
+ <>
+
+ setIsOpen(!isOpen)}>
+
+
+
+ {options.map((option) => (
+ handleOptionClick(option)}
+ >
+ {option}
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/src/components/common/Select/Select.style.jsx b/src/components/common/Select/Select.style.jsx
new file mode 100644
index 00000000..2aff2f3d
--- /dev/null
+++ b/src/components/common/Select/Select.style.jsx
@@ -0,0 +1,61 @@
+import styled from "styled-components";
+import theme from "../../../style/theme";
+
+export const DropDown = styled.div`
+ width: 139px;
+ height: 92px;
+ position: relative;
+`;
+
+export const Selected = styled.div`
+ background-color: ${theme.color.white};
+ cursor: pointer;
+ border: ${({ $isOpen }) =>
+ $isOpen
+ ? `2px solid ${theme.color.gray500}`
+ : `1px solid ${theme.color.gray400}`};
+ border-radius: 8px;
+ padding: 16px 12px;
+ color: ${theme.color.gray400};
+ display: flex;
+ justify-content: space-between;
+ z-index: 99;
+`;
+
+export const Options = styled.ul`
+ display: ${({ $isOpen }) => ($isOpen ? "block" : "none")};
+ background-color: ${theme.color.white};
+ color: ${theme.color.gray400};
+ list-style: none;
+ border-radius: 8px;
+ border: 1px solid ${theme.color.gray200};
+ text-align: center;
+ padding: 0px;
+ width: 139px;
+ position: absolute;
+ top: 100%;
+ right: 0;
+ transform: translateY(8px);
+ z-index: 101;
+`;
+export const OptionList = styled.li`
+ width: 100%;
+ height: 46px;
+ padding: 16px 12px;
+ border-radius: 8px;
+ background-color: ${theme.color.white};
+ &:hover {
+ background-color: ${theme.color.gray100};
+ }
+`;
+
+export const EditDropDown = styled.div`
+ position: relative;
+ height: 24px;
+`;
+export const KebobButton = styled.div`
+ background-color: ${theme.color.white};
+ cursor: pointer;
+ height: 24px;
+ border: "none";
+`;
diff --git a/src/components/profile-favoriteCount/profile.favorite.jsx b/src/components/profile-favoriteCount/profile.favorite.jsx
new file mode 100644
index 00000000..e69de29b
diff --git a/src/components/profile-favoriteCount/profile.favorite.style.jsx b/src/components/profile-favoriteCount/profile.favorite.style.jsx
new file mode 100644
index 00000000..e245c1ad
--- /dev/null
+++ b/src/components/profile-favoriteCount/profile.favorite.style.jsx
@@ -0,0 +1,2 @@
+import styled from "styled-components";
+import theme from "../../style/theme";
diff --git a/src/constants/globalConstant.jsx b/src/constants/globalConstant.jsx
index 16434e99..718f1f11 100644
--- a/src/constants/globalConstant.jsx
+++ b/src/constants/globalConstant.jsx
@@ -4,8 +4,14 @@ export const placeholder = {
content: "상품 소개를 입력해주세요",
price: "판매 가격을 입력해주세요",
tag: "태그를 입력해주세요",
+ createComment:
+ "개인정보를 공유 및 요청하거나, 명예 훼손, 무단 광고, 불법 정보 유포시 모니터링 후 삭제될 수 있으며, 이에 대한 민형사상 책임은 게시자에게 있습니다.",
};
export const button = {
send: "등록",
+ edit: "수정하기",
+ delete: "삭제하기",
+ cancel: "취소",
+ editConfirm: "수정 완료",
};
diff --git a/src/index.js b/src/index.js
index b1170bcf..b68680f7 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,6 @@
import React from "react";
import ReactDOM from "react-dom/client";
-import Main from "./components/Main";
+import Main from "./Main.jsx";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
);
diff --git a/src/pages/AddItem/AddItem.jsx b/src/pages/AddItem/AddItem.jsx
index 5dc50812..257f32a9 100644
--- a/src/pages/AddItem/AddItem.jsx
+++ b/src/pages/AddItem/AddItem.jsx
@@ -12,13 +12,15 @@ const INITIAL_DATA = {
price: 0,
tags: [],
};
+const REQUIRED_INPUT = ["name", "content", "tags", "price"];
+//
function AddItem() {
const [tag, setTag] = useState("");
const [formData, setFormData] = useState(INITIAL_DATA);
//
- const handleChange = (target) => {
- setFormData({ ...formData, [target.name]: target.value });
- //ToDo: img 프로퍼티에는 imgInput에서 넘겨준 file 객체가 담기고 있음 추후 백엔드 확인해야함
+ const handleChange = (e) => {
+ setFormData({ ...formData, [e.target.name]: e.target.value });
+ //ToDo: img 프로퍼티에는 imgInput에서 넘겨준 file 객체가 담기고 있음
};
const CreateTag = (e) => {
@@ -40,8 +42,7 @@ function AddItem() {
setFormData((prev) => ({ ...prev, tags: filterTags }));
};
- const requiredInput = ["name", "content", "tags", "price"];
- const isInputValid = requiredInput.every((field) => {
+ const isInputValid = REQUIRED_INPUT.every((field) => {
const value = formData[field];
switch (typeof value) {
case "object":
@@ -69,7 +70,6 @@ function AddItem() {
setTag(e.target.value)}
/>
-
- {formData.tags &&
- formData.tags.map((tag) => {
- return (
-
-
-
- );
- })}
-
+
diff --git a/src/pages/AddItem/AddItem.style.jsx b/src/pages/AddItem/AddItem.style.jsx
index 85cefafb..0fe592a5 100644
--- a/src/pages/AddItem/AddItem.style.jsx
+++ b/src/pages/AddItem/AddItem.style.jsx
@@ -54,9 +54,3 @@ export const TagInputContainer = styled.div`
flex-direction: column;
gap: 14px;
`;
-
-export const TagsContainer = styled.div`
- width: 100%;
- display: flex;
- gap: 12px;
-`;
diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js
deleted file mode 100644
index 574e911a..00000000
--- a/src/pages/HomePage.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import { useEffect, useState } from "react";
-import ItemsList from "../components/ItemsList";
-import { getProducts, bestProducts } from "../api";
-import { Link } from "react-router-dom";
-import PageCount from "../components/pageCount";
-import styled from "styled-components";
-import useWindowSize from "../hooks/useWindowSize";
-import SelectBox from "../components/SelectBox";
-
-//
-
-const Contents = styled.div`
- width: ${({ device }) =>
- device === "desktop" ? "1200px" : device === "tablet" ? "696px" : "344px"};
- margin: 70px auto;
-`;
-const InputDiv = styled.div`
- display: flex;
- justify-content: space-between;
- width: 100%;
- margin: 0 auto;
- flex-wrap: wrap;
- gap: 12px;
-`;
-
-const SearchBtn = styled.button`
- height: 42px;
- padding: 12px 23px;
- border-radius: 8px;
- color: #f3f4f6;
- background-color: #3692ff;
- border: none;
- font-size: 16px;
- font-weight: 600;
- line-height: 26px;
- order: ${({ device }) => (device === "mobile" ? 1 : "auto")};
-`;
-
-const InputForm = styled.input`
- width: ${({ device }) =>
- device === "desktop" ? "325px" : device === "tablet" ? "242px" : "288px"};
- height: 42px;
- padding: 9px 20px 9px 16px;
- border-radius: 12px;
- border: none;
- background-color: #f3f4f6;
- color: #9ca3af;
- font-size: 16px;
- font-weight: 400;
- line-height: 26px;
- text-align: left;
- order: ${({ device }) => (device === "mobile" ? 2 : "auto")};
- margin-left: ${({ device }) =>
- device === "desktop" ? "500px" : device === "tablet" ? "80px" : "0px"};
-`;
-
-//
-function HomePage() {
- const [items, setItems] = useState([]);
- const [selectedOrder, setSelectedOrder] = useState("최신순");
- const [bestItems, setBestItems] = useState([]);
- const [page, setPage] = useState(1);
- const device = useWindowSize();
- //
- const handleChangeSort = (selected) => {
- const { label } = selected;
- setSelectedOrder(label);
- };
-
- const handleLoad = async (options) => {
- const { list: bestItems } = await bestProducts(options);
- const { list } = await getProducts(options);
- setItems(list);
- setBestItems(bestItems);
- };
-
- const handleClickPageChange = (value) => {
- setPage(Number(value));
- };
-
- useEffect(() => {
- handleLoad({ selectedOrder, device, page });
- }, [selectedOrder, page, device]);
-
- return (
-
-
-
베스트 상품
-
-
-
- 전체 상품
-
-
- 상품 등록하기
-
-
-
-
-
-
- );
-}
-
-export default HomePage;
diff --git a/src/pages/HomePage/HomePage.jsx b/src/pages/HomePage/HomePage.jsx
new file mode 100644
index 00000000..b5f759e1
--- /dev/null
+++ b/src/pages/HomePage/HomePage.jsx
@@ -0,0 +1,64 @@
+import { useEffect, useState } from "react";
+import { Link } from "react-router-dom";
+//
+import * as S from "./HomePage.style";
+import ItemsList from "../ItemsPage/ItemsList";
+import { getProducts, bestProducts } from "../../api/product.api";
+import PageCount from "../../components/pageCount";
+import useWindowSize from "../../hooks/useWindowSize";
+import { SortSelect } from "../../components/common/Select/Select";
+//
+
+//
+function HomePage() {
+ const [items, setItems] = useState([]);
+ const [selectedOrder, setSelectedOrder] = useState("최신순");
+ const [bestItems, setBestItems] = useState([]);
+ const [page, setPage] = useState(1);
+ const device = useWindowSize();
+ //
+ const handleChangeSort = (selected) => {
+ const { label } = selected;
+ setSelectedOrder(label);
+ };
+
+ const handleLoad = async (options) => {
+ const { list: bestItems } = await bestProducts(options);
+ const { list } = await getProducts(options);
+ setItems(list);
+ setBestItems(bestItems);
+ };
+
+ const handleClickPageChange = (value) => {
+ setPage(Number(value));
+ };
+
+ useEffect(() => {
+ handleLoad({ selectedOrder, device, page });
+ }, [selectedOrder, page, device]);
+
+ return (
+
+
+
베스트 상품
+
+
+
+ 전체 상품
+
+
+ 상품 등록하기
+
+
+
+
+
+
+ );
+}
+
+export default HomePage;
diff --git a/src/pages/HomePage/HomePage.style.jsx b/src/pages/HomePage/HomePage.style.jsx
new file mode 100644
index 00000000..57633dec
--- /dev/null
+++ b/src/pages/HomePage/HomePage.style.jsx
@@ -0,0 +1,46 @@
+import styled from "styled-components";
+
+export const Contents = styled.div`
+ width: ${({ device }) =>
+ device === "desktop" ? "1200px" : device === "tablet" ? "696px" : "344px"};
+ margin: 70px auto;
+`;
+export const InputDiv = styled.div`
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+ margin: 0 auto;
+ flex-wrap: wrap;
+ gap: 12px;
+`;
+
+export const SearchBtn = styled.button`
+ height: 42px;
+ padding: 12px 23px;
+ border-radius: 8px;
+ color: #f3f4f6;
+ background-color: #3692ff;
+ border: none;
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 26px;
+ order: ${({ device }) => (device === "mobile" ? 1 : "auto")};
+`;
+
+export const InputForm = styled.input`
+ width: ${({ device }) =>
+ device === "desktop" ? "325px" : device === "tablet" ? "242px" : "288px"};
+ height: 42px;
+ padding: 9px 20px 9px 16px;
+ border-radius: 12px;
+ border: none;
+ background-color: #f3f4f6;
+ color: #9ca3af;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 26px;
+ text-align: left;
+ order: ${({ device }) => (device === "mobile" ? 2 : "auto")};
+ margin-left: ${({ device }) =>
+ device === "desktop" ? "500px" : device === "tablet" ? "80px" : "0px"};
+`;
diff --git a/src/components/ItemsList.js b/src/pages/ItemsPage/ItemsList.js
similarity index 90%
rename from src/components/ItemsList.js
rename to src/pages/ItemsPage/ItemsList.js
index 317361b3..65a37d41 100644
--- a/src/components/ItemsList.js
+++ b/src/pages/ItemsPage/ItemsList.js
@@ -1,7 +1,8 @@
-import favoriteImg from "../assets/favoriteLogo.svg";
+import { useNavigate } from "react-router-dom";
import styled from "styled-components";
-import useWindowSize from "../hooks/useWindowSize";
+import useWindowSize from "../../hooks/useWindowSize";
//
+import BtnHeart from "../../components/common/BtnHeart/BtnHeart";
const ByDevice = {
best: {
@@ -102,9 +103,10 @@ const Item = styled.div`
//
function ListItem({ value, items }) {
+ const navigate = useNavigate();
const device = useWindowSize();
return (
-
-
+
- navigate(`./${items.id}`)}>
{items.name}
{items.price} 원
-

-
{items.favoriteCount}
+
diff --git a/src/pages/ProductPage/Comments.jsx b/src/pages/ProductPage/Comments.jsx
new file mode 100644
index 00000000..49b80356
--- /dev/null
+++ b/src/pages/ProductPage/Comments.jsx
@@ -0,0 +1,67 @@
+import { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+//
+import * as S from "./Comments.style";
+import { placeholder } from "../../constants/globalConstant";
+import { button } from "../../constants/globalConstant";
+import noComment from "../../assets/no-comments.svg";
+import returnIcon from "../../assets/icons/return.icon.svg";
+//
+import { getProductComments } from "../../api/comment.api";
+import { Input } from "../../components/common/Input/Input";
+import Button from "../../components/common/Button/Button";
+import CommentCard from "../../components/CommentCard/CommentCard";
+//
+
+export default function Comments({ productId }) {
+ const [isDisabled, setIsDisabled] = useState(true);
+ const [comments, setComments] = useState({});
+ const [formData, setFormData] = useState("");
+ const navigate = useNavigate();
+ const handleLoad = async () => {
+ const data = await getProductComments(productId);
+ setComments(data);
+ };
+ const handleChange = () => {
+ setIsDisabled(false);
+ };
+ const handleClickSubmit = () => {};
+ useEffect(() => {
+ handleLoad();
+ }, []);
+ return (
+
+
+
+
+
+
+
+ {comments?.list?.length === 0 ? (
+
+
+
+ 아직 문의가 없어요
+
+
+ ) : (
+
+ {comments?.list?.map((data) => (
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/src/pages/ProductPage/Comments.style.jsx b/src/pages/ProductPage/Comments.style.jsx
new file mode 100644
index 00000000..4affe503
--- /dev/null
+++ b/src/pages/ProductPage/Comments.style.jsx
@@ -0,0 +1,50 @@
+import styled from "styled-components";
+import theme from "../../style/theme";
+//
+
+export const CommentWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 24px;
+ @media (max-width: 768px) {
+ gap: 40px;
+ }
+`;
+export const InputWrapper = styled.div`
+ width: 100%;
+
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+`;
+export const NoCommentWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 48px;
+`;
+export const NoCommentImgWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ color: ${theme.color.gray400};
+`;
+export const NoCommentImg = styled.img`
+ width: 196px;
+ aspect-ratio: 1/1;
+`;
+
+export const ButtonWrapper = styled.div`
+ margin-left: auto;
+ width: 74px;
+ height: 42px;
+`;
+
+export const CommentCardContainer = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+`;
diff --git a/src/pages/ProductPage/Product.jsx b/src/pages/ProductPage/Product.jsx
new file mode 100644
index 00000000..a13ff4ab
--- /dev/null
+++ b/src/pages/ProductPage/Product.jsx
@@ -0,0 +1,23 @@
+import { useEffect, useState } from "react";
+import { useParams } from "react-router-dom";
+//
+import ProductInfo from "./ProductInfo";
+import Comments from "./Comments";
+import * as S from "./Product.style";
+//
+export default function Product() {
+ const { productId } = useParams();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/ProductPage/Product.style.jsx b/src/pages/ProductPage/Product.style.jsx
new file mode 100644
index 00000000..0560a3e3
--- /dev/null
+++ b/src/pages/ProductPage/Product.style.jsx
@@ -0,0 +1,25 @@
+import styled from "styled-components";
+
+export const PageWrapper = styled.div`
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ //TODO전역 스타일 화 하기
+`;
+export const ContentsWrapper = styled.div`
+ width: 1200px;
+ padding: 24px;
+`;
+export const Contents = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 80px;
+ @media (max-width: 375px) {
+ gap: 24px;
+ }
+ @media (min-width: 376px) and (max-width: 768px) {
+ gap: 40px;
+ }
+`;
diff --git a/src/pages/ProductPage/ProductInfo.jsx b/src/pages/ProductPage/ProductInfo.jsx
new file mode 100644
index 00000000..f3089b26
--- /dev/null
+++ b/src/pages/ProductPage/ProductInfo.jsx
@@ -0,0 +1,71 @@
+import { useState, useEffect } from "react";
+//
+import profile from "../../assets/icons/default.profile.icon.svg";
+//
+import { getProductInfo } from "../../api/product.api";
+import * as S from "./ProductInfo.style";
+import Tag from "../../components/Tag/Tag";
+import BtnHeart from "../../components/common/BtnHeart/BtnHeart";
+import { EditSelect } from "../../components/common/Select/Select";
+import { useFormatDate, useFormatPrice } from "../../hooks/useFormatting";
+//
+
+export default function ProductInfo({ productId }) {
+ const [product, setProduct] = useState({});
+ const formattedDate = useFormatDate(product.createdAt);
+ const formattedPrice = useFormatPrice(product.price, "KRW");
+ //
+ const handleLoad = async () => {
+ const info = await getProductInfo(productId);
+ setProduct(info);
+ };
+
+ useEffect(() => {
+ handleLoad();
+ }, []);
+ const handleOnChange = () => {};
+ return (
+
+ {product?.images && product?.images?.length > 0 ? (
+
+
+
+ ) : (
+ 이미지가 없습니다.
+ )}
+
+
+
+
+ {product.name}
+ {formattedPrice}원
+
+
+
+
+
+ 상품소개
+ {product.description}
+
+
+ 상품 태그
+
+
+
+
+
+
+
+
+ {product.ownerNickname}
+ {formattedDate}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/ProductPage/ProductInfo.style.jsx b/src/pages/ProductPage/ProductInfo.style.jsx
new file mode 100644
index 00000000..671e6105
--- /dev/null
+++ b/src/pages/ProductPage/ProductInfo.style.jsx
@@ -0,0 +1,144 @@
+import styled from "styled-components";
+import theme from "../../style/theme";
+//
+export const ProductInfoWrapper = styled.div`
+ display: flex;
+ gap: 24px;
+ padding-bottom: 40px;
+ border-bottom: 1px solid ${theme.color.gray200};
+ @media (max-width: 375px) {
+ flex-direction: column;
+ padding-bottom: 24px;
+ gap: 16px;
+ }
+ @media (min-width: 375px) and (max-width: 590px) {
+ flex-direction: column;
+ padding-bottom: 32px;
+ gap: 16px;
+ }
+ @media (min-width: 591px) and (max-width: 768px) {
+ gap: 16px;
+ padding-bottom: 32px;
+ }
+`;
+
+export const ProductImg = styled.img`
+ width: 100%;
+ height: 100%;
+ border-radius: 16px;
+`;
+
+export const ImgDiv = styled.div`
+ width: 486px;
+ height: 486px;
+ aspect-ratio: 1/1;
+ @media (max-width: 375px) {
+ min-width: 343px;
+ min-height: 343px;
+ }
+ @media (min-width: 375px) and (max-width: 590px) {
+ min-width: 343px;
+ min-height: 343px;
+ margin: 0px auto;
+ }
+ @media (min-width: 376px) and (max-width: 768px) {
+ max-width: 340px;
+ max-height: 340px;
+ }
+`;
+export const InfoProfileWrapper = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 62px;
+`;
+export const InfoTagWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+`;
+
+export const TitleEditBtnWrapper = styled.div`
+ display: flex;
+ justify-content: space-between;
+ padding-bottom: 16px;
+ border-bottom: 1px solid ${theme.color.gray200};
+`;
+export const TitlePriceWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+`;
+export const ProductTitle = styled.p`
+ font: ${theme.font.H2Bold};
+ @media (max-width: 375px) {
+ font: ${theme.font.H5Bold};
+ }
+ @media (min-width: 376px) and (max-width: 768px) {
+ font: ${theme.font.H3Bold};
+ }
+`;
+
+export const ProductPrice = styled.p`
+ font: ${theme.font.H1};
+ @media (max-width: 375px) {
+ font: ${theme.font.H2Bold};
+ }
+ @media (min-width: 376px) and (max-width: 768px) {
+ font-size: 32px;
+ }
+`;
+
+export const DesTitleContentWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ @media (max-width: 375px) {
+ gap: 8px;
+ }
+`;
+export const DescriptionTitle = styled.p`
+ font: ${theme.font.H5Bold};
+ color: ${theme.color.gray600}; //4b5563
+ @media (max-width: 768px) {
+ font: ${theme.font.H7Bold};
+ }
+`;
+
+export const ProductContent = styled.div`
+ overflow-y: scroll;
+ height: 104px;
+ font: ${theme.font.H5Regular};
+ @media (max-width: 768px) {
+ height: 156px;
+ }
+`;
+export const ProfileFavorite = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+`;
+export const ProfileWrapper = styled.div`
+ display: flex;
+ gap: 16px;
+`;
+export const BorderLeft = styled.div`
+ border-left: 1px solid ${theme.color.gray200};
+ padding-left: 24px;
+ height: 34px;
+`;
+export const ProfileImg = styled.img`
+ width: 40px;
+ aspect-ratio: 1/1;
+`;
+export const NickNameDate = styled.div`
+ display: flex;
+`;
+export const NickName = styled.p`
+ font: ${theme.font.H7Medium};
+ color: ${theme.color.gray600};
+`;
+export const CreatedAt = styled.p`
+ font: ${theme.font.H7Regular};
+ color: ${theme.color.gray400};
+`;
diff --git a/src/style/theme.jsx b/src/style/theme.jsx
index 4f5f785e..b9d2a023 100644
--- a/src/style/theme.jsx
+++ b/src/style/theme.jsx
@@ -4,7 +4,7 @@ const theme = {
gray800: "#1f2937",
gray700: "#374151",
gray600: "#4b5563",
- gray500: "#6b7280",
+ gray500: "#737373",
gray400: "#9ca3af",
gray200: "#e5e7eb",
gray100: "#f3f4f6",
@@ -15,5 +15,23 @@ const theme = {
backgroundLightGray: "#fcfcfc",
inputRed: "#f74747",
},
+ font: {
+ //weight , size , height , fontfamily
+ H1: "600 40px/47px 'Pretendard', sans-serif",
+ H2Bold: "600 24px/32px 'Pretendard', sans-serif",
+ H2Regular: "400 24px/36px 'Pretendard', sans-serif",
+ H3Bold: "600 20px/30px 'Pretendard', sans-serif",
+ H3Regular: "400 20px/30px 'Pretendard', sans-serif",
+ H4Bold: "600 18px/28px 'Pretendard', sans-serif",
+ H4Regular: "400 18px/28px 'Pretendard', sans-serif",
+ H5Bold: "600 16px/26px 'Pretendard', sans-serif",
+ H5Regular: "400 16px/26px 'Pretendard', sans-serif",
+ H6Bold: "600 15px/22px 'Pretendard', sans-serif",
+ H6Regular: "400 15px/22px 'Pretendard', sans-serif",
+ H7Bold: "600 14px/20px 'Pretendard', sans-serif",
+ H7Medium: "500 14px/24px 'Pretendard', sans-serif",
+ H7Regular: "400 14px/24px 'Pretendard', sans-serif",
+ H8: "400 12px/18px 'Pretendard', sans-serif",
+ },
};
export default theme;