-
©codeit - 2024
-
-
Privacy Policy
-
FAQ
+
+
+
©codeit - 2024
+
+ Privacy Policy
+ FAQ
+
+
-
-
- {snsImages.map((sns, index) => (
- -
-
-
-
-
- ))}
-
-
+
);
}
diff --git a/src/components/layout/HomeHeader.jsx b/src/components/layout/HomeHeader.jsx
index 85b628d2..7675a2e7 100644
--- a/src/components/layout/HomeHeader.jsx
+++ b/src/components/layout/HomeHeader.jsx
@@ -1,11 +1,12 @@
import { Link } from "react-router-dom";
import pandaLogo from "@/assets/logo.png";
import { HeaderStyle } from "@/components/layout/Header.style";
+import { WidthContainer } from "@/styles/commonStyle";
function HomeHeader() {
return (
-
+
판다마켓
@@ -13,7 +14,7 @@ function HomeHeader() {
로그인
-
+
);
}
diff --git a/src/components/layout/SnsList.jsx b/src/components/layout/SnsList.jsx
new file mode 100644
index 00000000..06325884
--- /dev/null
+++ b/src/components/layout/SnsList.jsx
@@ -0,0 +1,20 @@
+import { snsImages } from "@/components/layout/snsData";
+
+export default function SnsList() {
+ return (
+ <>
+
+ {snsImages.map((sns, index) => {
+ const SnsIcon = sns.svg;
+ return (
+ -
+
+
+
+
+ );
+ })}
+
+ >
+ );
+}
diff --git a/src/components/layout/snsData.js b/src/components/layout/snsData.js
index eeba1577..8a51ac9e 100644
--- a/src/components/layout/snsData.js
+++ b/src/components/layout/snsData.js
@@ -1,27 +1,27 @@
-import imgSNS01 from "@/assets/sns01.png";
-import imgSNS02 from "@/assets/sns02.png";
-import imgSNS03 from "@/assets/sns03.png";
-import imgSNS04 from "@/assets/sns04.png";
+import imgSNS01 from "@/assets/ic_sns01.svg";
+import imgSNS02 from "@/assets/ic_sns02.svg";
+import imgSNS03 from "@/assets/ic_sns03.svg";
+import imgSNS04 from "@/assets/ic_sns04.svg";
export const snsImages = [
{
href: "https://www.facebook.com/",
- img: imgSNS01,
+ svg: imgSNS01,
alt: "facebook",
},
{
href: "https://x.com/",
- img: imgSNS02,
+ svg: imgSNS02,
alt: "X",
},
{
href: "https://www.youtube.com/",
- img: imgSNS03,
+ svg: imgSNS03,
alt: "youtube",
},
{
href: "https://www.instagram.com/",
- img: imgSNS04,
+ svg: imgSNS04,
alt: "instagram",
},
];
diff --git a/src/layout/NoLayout.jsx b/src/layout/NoLayout.jsx
deleted file mode 100644
index fd51323b..00000000
--- a/src/layout/NoLayout.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Outlet } from "react-router-dom";
-
-function NoLayout() {
- return
;
-}
-
-export default NoLayout;
diff --git a/src/pages/AddItem.jsx b/src/pages/AddItem.jsx
deleted file mode 100644
index 4d635afd..00000000
--- a/src/pages/AddItem.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function AddItem() {
- return (
- <>
-
- >
- );
-}
diff --git a/src/pages/addItem/AddImage.jsx b/src/pages/addItem/AddImage.jsx
new file mode 100644
index 00000000..8062c105
--- /dev/null
+++ b/src/pages/addItem/AddImage.jsx
@@ -0,0 +1,114 @@
+import AddIcon from "@/assets/ic_plus.svg";
+import XIcon from "@/assets/ic_close.svg";
+import { useRef, useState } from "react";
+import apiRequest from "@/apis/apiRequest";
+import styled from "styled-components";
+import { pxToRem } from "@/utils/pxToRem";
+import { flexCenter, fullSize, textStyles } from "@/styles/commonStyle";
+
+export default function AddImage({ imgTitle = "상품이미지", onChange }) {
+ const [imageUrl, setImageUrl] = useState(null);
+ const fileInputRef = useRef(null);
+
+ const handleFileChange = async (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
+
+ const formData = new FormData();
+ formData.append("image", file);
+
+ try {
+ const data = await apiRequest("/images/upload", {
+ method: "POST",
+ body: formData,
+ headers: {},
+ });
+ setImageUrl(data.url);
+ onChange?.(data.url);
+ } catch (error) {
+ console.error("이미지 등록 실패:", error);
+ }
+ };
+
+ const handleClick = () => {
+ fileInputRef.current.click();
+ };
+
+ const handleRemove = () => {
+ setImageUrl(null);
+ fileInputRef.current.value = "";
+ onChange?.(null);
+ };
+
+ return (
+
+
+
+
+ {imageUrl ? (
+
+

+
+
+ ) : (
+
+ )}
+
+
+
+
+
+ );
+}
+
+export const AddImageStyle = styled.div`
+ #item_img {
+ position: relative;
+ width: ${pxToRem(282)};
+ height: ${pxToRem(282)};
+ cursor: pointer;
+ margin: 1rem 0 2rem;
+ }
+
+ .image_add {
+ ${fullSize}
+ }
+ .image_add.btn {
+ position: relative;
+ }
+
+ .icon_position {
+ height: 100%;
+ ${flexCenter}
+ flex-direction: column;
+ }
+
+ .close.bnt {
+ position: absolute;
+ background-color: transparent;
+ width: ${pxToRem(20)};
+ height: ${pxToRem(20)};
+ right: ${pxToRem(13)};
+ top: ${pxToRem(14)};
+ }
+`;
diff --git a/src/pages/addItem/InputField.jsx b/src/pages/addItem/InputField.jsx
new file mode 100644
index 00000000..50f5aa82
--- /dev/null
+++ b/src/pages/addItem/InputField.jsx
@@ -0,0 +1,37 @@
+import { useState } from "react";
+
+export default function InputField({
+ id,
+ label,
+ type = "text",
+ placeholder,
+ errorMessage,
+ ...inputProps
+}) {
+ const [touched, setTouched] = useState(false);
+
+ const handleBlur = (e) => {
+ setTouched(true);
+ if (inputProps.onBlur) inputProps.onBlur(e);
+ };
+
+ const value = inputProps.value || "";
+
+ let errors =
+ touched && (!value.trim() || (type === "number" && !/^\d+$/.test(value)));
+
+ return (
+
+
+
+ {errors && {errorMessage}}
+
+ );
+}
diff --git a/src/pages/addItem/InputTag.jsx b/src/pages/addItem/InputTag.jsx
new file mode 100644
index 00000000..364a5787
--- /dev/null
+++ b/src/pages/addItem/InputTag.jsx
@@ -0,0 +1,94 @@
+import XIcon from "@/assets/ic_close.svg";
+import { flexCenter } from "@/styles/commonStyle";
+import { pxToRem } from "@/utils/pxToRem";
+import { useState } from "react";
+import styled from "styled-components";
+
+export default function InputTag({ onChange }) {
+ const [tags, setTags] = useState([]);
+ const [inputValue, setInputValue] = useState("");
+ const [includeValue, setIncludeValue] = useState(false);
+
+ const handleKeyDown = (e) => {
+ if (e.key === "Enter" && inputValue.trim()) {
+ e.preventDefault();
+
+ if (tags.some((tag) => tag.label === inputValue.trim())) {
+ setIncludeValue(true);
+ return;
+ }
+
+ const new_tag = {
+ id: crypto.randomUUID(),
+ label: inputValue.trim(),
+ };
+
+ const newTags = [...tags, new_tag];
+ setTags(newTags);
+ onChange?.(newTags.map((tag) => tag.label));
+ setInputValue("");
+ }
+ };
+
+ const removeTag = (id) => {
+ const newTags = tags.filter((tag) => tag.id !== id);
+ setTags(newTags);
+ onChange?.(newTags.map((tag) => tag.label));
+ };
+
+ return (
+
+
+
+ setInputValue(e.target.value)}
+ onKeyDown={handleKeyDown}
+ />
+ {includeValue && (
+ 이미 입력된 태그입니다
+ )}
+
+
+ {tags.map((tag) => (
+ -
+ #{tag.label}
+
+
+ ))}
+
+
+
+ );
+}
+
+const TagStyle = styled.div`
+ .input_style {
+ margin-bottom: ${pxToRem(14)};
+ }
+
+ .tags {
+ display: flex;
+ align-items: center;
+ flex-wrap: nowrap;
+ gap: ${pxToRem(12)};
+ }
+
+ .tag {
+ ${flexCenter}
+ line-height: ${pxToRem(36)};
+ background-color: var(--gray-100);
+ padding: 0 1rem;
+ border-radius: ${pxToRem(26)};
+ }
+
+ .tag_icon {
+ background-color: transparent;
+ }
+`;
diff --git a/src/pages/addItem/index.jsx b/src/pages/addItem/index.jsx
new file mode 100644
index 00000000..cc9cf5f1
--- /dev/null
+++ b/src/pages/addItem/index.jsx
@@ -0,0 +1,109 @@
+import { postProduct } from "@/apis/products";
+import AddImage from "@/pages/addItem/AddImage";
+import { AddItemStyle } from "@/pages/addItem/index.style";
+import InputField from "@/pages/addItem/InputField";
+import InputTag from "@/pages/addItem/InputTag";
+import { WidthContainer } from "@/styles/commonStyle";
+import { useState } from "react";
+import { useNavigate } from "react-router-dom";
+
+export default function AddItem() {
+ const navigate = useNavigate();
+
+ const [form, setForm] = useState({
+ itemName: "",
+ itemIntroduction: "",
+ itemPrice: "",
+ images: [],
+ tags: [],
+ });
+
+ const errorMessage = {
+ itemName: "상품명을 입력해주세요",
+ itemIntroduction: "상품 소개를 입력해주세요",
+ itemPrice: "숫자로 된 판매 가격을 입력해주세요",
+ };
+
+ const handleChange = (e) => {
+ const { name, value } = e.target;
+ setForm((prev) => ({ ...prev, [name]: value }));
+ };
+
+ const isFormValid =
+ form.itemName.trim() &&
+ form.itemIntroduction.trim() &&
+ form.itemPrice.trim();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ try {
+ const data = {
+ name: form.itemName,
+ description: form.itemIntroduction,
+ price: Number(form.itemPrice),
+ images: form.images,
+ tags: form.tags,
+ };
+ await postProduct(data);
+
+ navigate("/items");
+ } catch (error) {
+ console.error("상품 등록 실패:", error);
+ }
+ };
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/pages/addItem/index.style.js b/src/pages/addItem/index.style.js
new file mode 100644
index 00000000..05de0ab8
--- /dev/null
+++ b/src/pages/addItem/index.style.js
@@ -0,0 +1,69 @@
+import { pxToRem } from "@/utils/pxToRem";
+import styled from "styled-components";
+import "@/styles/variables.css";
+import { textStyles } from "@/styles/commonStyle";
+
+export const AddItemStyle = styled.div`
+ .input_list {
+ margin-bottom: ${pxToRem(60)};
+ }
+
+ .item_add {
+ display: flex;
+ justify-content: space-between;
+ margin: ${pxToRem(50)} 0 ${pxToRem(24)};
+ }
+ .item_submit {
+ width: ${pxToRem(74)};
+ height: ${pxToRem(42)};
+ border-radius: var(--border-8);
+ }
+
+ .item_submit:disabled {
+ background-color: var(--gray-400);
+ cursor: unset;
+ }
+
+ label {
+ ${textStyles["text-2lg-bold"]}
+ }
+
+ .image_add,
+ .input_style {
+ background-color: var(--gray-100);
+ border-radius: ${pxToRem(12)};
+ ${textStyles["text-lg-regular"]}
+ }
+
+ .image_add,
+ .input_style::placeholder {
+ color: var(--gray-400);
+ }
+
+ .input_style {
+ display: block;
+ width: 100%;
+ outline: none;
+ line-height: ${pxToRem(56)};
+ padding: 0 ${pxToRem(24)};
+ margin: 1rem 0 2rem;
+ color: var(--gray-800);
+ }
+
+ .has_error {
+ position: relative;
+ }
+
+ .has_error .input_style {
+ outline: 2px solid var(--error-red);
+ box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.08);
+ }
+
+ .errorText {
+ position: absolute;
+ display: block;
+ bottom: ${pxToRem(-30)};
+ color: var(--error-red);
+ ${textStyles["text-lg-regular"]}
+ }
+`;
diff --git a/src/pages/home/HomeEnding.jsx b/src/pages/home/HomeEnding.jsx
index e5955a40..ffb8eb15 100644
--- a/src/pages/home/HomeEnding.jsx
+++ b/src/pages/home/HomeEnding.jsx
@@ -1,18 +1,21 @@
-import EndingImage from "@/assets/Img_home_bottom.png";
+import EndingImage from "@/assets/home_bottom.png";
+import { WidthContainer } from "@/styles/commonStyle";
function HomeEnding() {
return (
-
-
-
- 믿을 수 있는
-
- 판다마켓 중고 거래
-
+
+
+
+
+ 믿을 수 있는
+
+ 판다마켓 중고 거래
+
+
+
-
-
+
);
}
diff --git a/src/pages/home/HomeIntro.jsx b/src/pages/home/HomeIntro.jsx
index 5193f649..62def1ee 100644
--- a/src/pages/home/HomeIntro.jsx
+++ b/src/pages/home/HomeIntro.jsx
@@ -1,19 +1,22 @@
import { Link } from "react-router-dom";
-import IntroImage from "@/assets/img_home_top.png";
+import IntroImage from "@/assets/home_top.png";
+import { WidthContainer } from "@/styles/commonStyle";
function HomeIntro() {
return (
-
-
-
일상의 모든 물건을 거래해 보세요
-
- 구매하러 가기
-
+
+
+
+
일상의 모든 물건을 거래해 보세요
+
+ 구매하러 가기
+
+
+
-
-
+
);
}
diff --git a/src/pages/home/HomeMain.jsx b/src/pages/home/HomeMain.jsx
index fa72ece1..ae3bfb78 100644
--- a/src/pages/home/HomeMain.jsx
+++ b/src/pages/home/HomeMain.jsx
@@ -1,17 +1,18 @@
import { homeCards } from "@/pages/home/homeCardData";
import HomeCard from "@/pages/home/HomeCard";
+import { WidthContainer } from "@/styles/commonStyle";
function HomeMain() {
return (
-
+
{homeCards.map((card) => (
))}
-
+
);
}
diff --git a/src/pages/home/homeCardData.js b/src/pages/home/homeCardData.js
index e6aad9a4..07b34bab 100644
--- a/src/pages/home/homeCardData.js
+++ b/src/pages/home/homeCardData.js
@@ -1,6 +1,6 @@
-import Homecard1 from "@/assets/Img_home_01.png";
-import Homecard2 from "@/assets/Img_home_02.png";
-import Homecard3 from "@/assets/Img_home_03.png";
+import Homecard1 from "@/assets/home_main01.png";
+import Homecard2 from "@/assets/home_main02.png";
+import Homecard3 from "@/assets/home_main03.png";
export const homeCards = [
{
diff --git a/src/pages/items/ItemList.jsx b/src/pages/items/ItemList.jsx
index e1ee581a..ce3ee924 100644
--- a/src/pages/items/ItemList.jsx
+++ b/src/pages/items/ItemList.jsx
@@ -1,30 +1,43 @@
-import { ItemListStyle } from "@/pages/items/ItemList.style";
-import noImage from "@/assets/NoImage.png";
-import favoriteIcon from "@/assets/favorit_Icon.png";
-import favoriteFillIcon from "@/assets/favorit_fill_Icon.png";
-import { useState } from "react";
-import { patchProduct } from "@/apis/products";
+import { useEffect, useState } from "react";
+import { deleteFavorite, getProductById, postFavorite } from "@/apis/products";
import { Link } from "react-router-dom";
+import { ItemListStyle } from "@/pages/items/ItemList.style";
+import noImage from "@/assets/noImage.png";
+import FavoriteIcon from "@/assets/ic_favorit_Icon.svg";
+import FavoriteFillIcon from "@/assets/ic_favorit_fill_Icon.svg";
+
export default function ItemList({ id, images, name, price, favoriteCount }) {
const [isClick, setIsClick] = useState(false);
const [count, setCount] = useState(favoriteCount);
const [loading, setLoading] = useState(false);
+ useEffect(() => {
+ async function fetchDetail() {
+ const product = await getProductById(id);
+ if (product) {
+ setIsClick(product.isFavorite);
+ setCount(product.favoriteCount);
+ }
+ }
+ fetchDetail();
+ }, [id]);
+
const handleFavoriteClick = async () => {
if (loading) return;
setLoading(true);
- setIsClick((prev) => !prev);
- const newCount = count + (!isClick ? +1 : -1);
+ const nextState = !isClick;
+ setIsClick(nextState);
+ const newCount = count + (nextState ? +1 : -1);
setCount(newCount);
try {
- await patchProduct(id, { favoriteCount: newCount });
+ nextState ? await postFavorite(id) : await deleteFavorite(id);
} catch (error) {
console.error("좋아요 변경 실패:", error);
- setIsClick(!isClick);
+ setIsClick(isClick);
setCount(count);
} finally {
setLoading(false);
@@ -45,16 +58,16 @@ export default function ItemList({ id, images, name, price, favoriteCount }) {
/>
-
+
{name}
-
+
{Number(price).toLocaleString()} 원
-
+ {isClick ? (
+
+ ) : (
+
+ )}
{count}
diff --git a/src/pages/items/ItemList.style.js b/src/pages/items/ItemList.style.js
index 7077076d..dadba7e6 100644
--- a/src/pages/items/ItemList.style.js
+++ b/src/pages/items/ItemList.style.js
@@ -1,4 +1,4 @@
-import { textStyles } from "@/styles/commomStyle";
+import { textStyles } from "@/styles/commonStyle";
import { pxToRem } from "@/utils/pxToRem";
import styled from "styled-components";
diff --git a/src/pages/items/ItemsOrder.jsx b/src/pages/items/ItemsOrder.jsx
index 8b321a0c..ade0db69 100644
--- a/src/pages/items/ItemsOrder.jsx
+++ b/src/pages/items/ItemsOrder.jsx
@@ -1,11 +1,11 @@
import styled from "styled-components";
-
+import ArrowIcon from "@/assets/ic_arrow_down.svg";
+import SortIcon from "@/assets/ic_sort.svg";
import { pxToRem } from "@/utils/pxToRem";
-import arrowIcon from "@/assets/arrow_icon.png";
-import { flexCenter, textStyles } from "@/styles/commomStyle";
+import { flexCenter, media, textStyles } from "@/styles/commonStyle";
import { useState } from "react";
-export default function ItemsOrder({ orderBy, setOrderBy }) {
+export default function ItemsOrder({ orderBy, setOrderBy, device }) {
const [isOpen, setIsOpen] = useState(false);
const handleSelect = (value) => {
@@ -17,8 +17,14 @@ export default function ItemsOrder({ orderBy, setOrderBy }) {
setIsOpen((prev) => !prev)}>
-
{orderBy === "recent" ? "최신순" : "인기순"}
-

+ {device === "mobile" ? (
+
+ ) : (
+ <>
+
{orderBy === "recent" ? "최신순" : "인기순"}
+
+ >
+ )}
{isOpen && (
@@ -57,6 +63,10 @@ const Div = styled.div`
.listLabel {
line-height: ${pxToRem(42)};
gap: ${pxToRem(28)};
+ ${media.mobile} {
+ width: ${pxToRem(42)};
+ height: ${pxToRem(42)};
+ }
}
.arrow_icon {
@@ -73,6 +83,9 @@ const Div = styled.div`
display: block;
text-align: center;
background-color: #fff;
+ ${media.mobile} {
+ right: 0;
+ }
}
.listOption:hover {
diff --git a/src/pages/items/ItemsSearch.jsx b/src/pages/items/ItemsSearch.jsx
index 92555b12..1c04a724 100644
--- a/src/pages/items/ItemsSearch.jsx
+++ b/src/pages/items/ItemsSearch.jsx
@@ -1,9 +1,11 @@
-import { flexCenter, textStyles } from "@/styles/commomStyle";
+import { flexCenter, fullSize, textStyles } from "@/styles/commonStyle";
import { pxToRem } from "@/utils/pxToRem";
import { useState } from "react";
// import { toast } from "react-toastify";
import styled from "styled-components";
+import SearchIcon from "@/assets/ic_search_gray.svg";
+
export default function ItemsSearch({ setSearchInput }) {
const [inputValue, setInputValue] = useState("");
@@ -20,6 +22,7 @@ export default function ItemsSearch({ setSearchInput }) {
};
const handleClick = () => {
+ // 빈칸 입력시 경고 토스트
// if (inputValue.trim() === "") {
// toast.warning("검색어를 입력해주세요", {
// toastId: "empty-search",
@@ -43,13 +46,27 @@ export default function ItemsSearch({ setSearchInput }) {
return (
);
}
@@ -60,17 +77,37 @@ const Div = styled.div`
height: ${pxToRem(42)};
padding: 0 ${pxToRem(13)};
}
-
- .search_input{
+
+ .search_wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding: 0;
+ }
+
+ .search_input {
+ ${fullSize}
+ padding-left: ${pxToRem(40)};
background-color: var(--gray-100);
border-radius: var(--border-10) 0 0 var(--border-10);
}
- .search_btn{
- ${textStyles["text-lg-medium"]}
+ .search_input:focus {
+ outline: none;
+ }
+
+ .search_icon {
+ position: absolute;
+ left: 1rem;
+ top: 50%;
+ transform: translateY(-50%);
+ pointer-events: none;
+ }
+
+ .search_btn {
+ ${textStyles["text-lg-medium"]}
background-color: var(--primary-100);
color: #fff;
border-radius: 0 var(--border-10) var(--border-10) 0;
}
-
`;
diff --git a/src/pages/items/PageList.jsx b/src/pages/items/PageList.jsx
index 8b6ac793..66e3cc7d 100644
--- a/src/pages/items/PageList.jsx
+++ b/src/pages/items/PageList.jsx
@@ -1,6 +1,6 @@
-import LArrow from "@/assets/left_arrow.png";
-import RArrow from "@/assets/right_arrow.png";
-import { flexCenter } from "@/styles/commomStyle";
+import LArrow from "@/assets/ic_arrow_left_active.svg";
+import RArrow from "@/assets/ic_arrow_right_active.svg";
+import { flexCenter } from "@/styles/commonStyle";
import { pxToRem } from "@/utils/pxToRem";
import styled from "styled-components";
@@ -34,7 +34,7 @@ export default function PageList({ totalCount, page, setPage, getAllList }) {
onClick={goPrevGroup}
disabled={!prevGroup}
>
-

+
{Array.from({ length: groupEnd - groupStart + 1 }, (_, i) => {
@@ -56,7 +56,7 @@ export default function PageList({ totalCount, page, setPage, getAllList }) {
onClick={goNextGroup}
disabled={!nextGroup}
>
-

+
@@ -71,9 +71,14 @@ const PageListStyle = styled.div`
}
.page_btn {
+ ${flexCenter}
border: 1px solid var(--gray-200);
}
+ .page_arrow_img > path {
+ transform: translate(${pxToRem(0.6)}, ${pxToRem(0.6)});
+ }
+
.page_list,
.page_btn {
outline: none;
@@ -91,9 +96,4 @@ const PageListStyle = styled.div`
.page_list:hover .page_btn.arrow {
background-color: transparent;
}
-
- .page_arrow_img {
- width: ${pxToRem(7)};
- height: ${pxToRem(12)};
- }
`;
diff --git a/src/pages/items/index.jsx b/src/pages/items/index.jsx
index 272a1313..ea032ffc 100644
--- a/src/pages/items/index.jsx
+++ b/src/pages/items/index.jsx
@@ -7,14 +7,15 @@ import { handleFetch } from "@/utils/handleFetch";
import ItemList from "@/pages/items/ItemList";
import ItemsSearch from "@/pages/items/ItemsSearch";
import ItemsOrder from "@/pages/items/ItemsOrder";
-import { ItemsStyle } from "@/pages/items/Items.style";
+import { ItemsStyle } from "@/pages/items/index.style";
import PageList from "@/pages/items/PageList";
+import { breakpoints, media } from "@/styles/commonStyle";
//반응형
const getDeviceType = () => {
const width = window.innerWidth;
- if (width >= 1200) return "pc";
- if (width >= 768) return "tablet";
+ if (width >= breakpoints.pc) return "pc";
+ if (width >= breakpoints.tablet) return "tablet";
return "mobile";
};
@@ -82,12 +83,6 @@ function ItemsPage() {
"best"
);
- //맨 처음 렌더링했을 때
- useEffect(() => {
- fetchProducts();
- fetchBestProducts();
- }, []);
-
// 페이지이동, 검색, 정렬변경, 화면 크기 변경했을 때 렌더링
useEffect(() => {
fetchProducts();
@@ -105,7 +100,7 @@ function ItemsPage() {
>
) : (
<>
-
베스트 상품
+
베스트 상품
{bestProducts.map((item) => (
-
@@ -116,13 +111,17 @@ function ItemsPage() {
-
전체 상품
+
전체 상품
-
-
-
-
+
+
diff --git a/src/pages/items/Items.style.js b/src/pages/items/index.style.js
similarity index 73%
rename from src/pages/items/Items.style.js
rename to src/pages/items/index.style.js
index bebbeb5c..b1003f7b 100644
--- a/src/pages/items/Items.style.js
+++ b/src/pages/items/index.style.js
@@ -1,4 +1,4 @@
-import { flexCenter, fullSize, media, textStyles } from "@/styles/commomStyle";
+import { flexCenter, media, textStyles } from "@/styles/commonStyle";
import { pxToRem } from "@/utils/pxToRem";
import styled from "styled-components";
@@ -7,8 +7,10 @@ export const ItemsStyle = styled.div`
margin: 0 auto;
padding: ${pxToRem(24)} 0 ${pxToRem(58)};
- h3 {
+ .itmes_title {
${textStyles["text-xl-bold"]}
+ line-height: ${pxToRem(42)};
+ margin-bottom: 0.5rem;
}
/* 상품 nav */
@@ -18,11 +20,21 @@ export const ItemsStyle = styled.div`
align-items: center;
margin-bottom: ${pxToRem(24)};
line-height: ${pxToRem(42)};
+
+ ${media.mobile} {
+ position: relative;
+ flex-wrap: wrap;
+ }
}
.item_order {
${flexCenter};
gap: ${pxToRem(12)};
+
+ ${media.mobile} {
+ width: 100%;
+ justify-content: space-between;
+ }
}
.item_all_list {
@@ -36,6 +48,12 @@ export const ItemsStyle = styled.div`
border-radius: var(--border-10);
background-color: var(--primary-100);
padding: 0 ${pxToRem(23)};
+
+ ${media.mobile} {
+ position: absolute;
+ right: 0;
+ top: 0;
+ }
}
/* 상품 이미지 */
@@ -44,6 +62,16 @@ export const ItemsStyle = styled.div`
gap: ${pxToRem(24)};
}
+ .product_img {
+ aspect-ratio: 1 / 1;
+ object-fit: cover;
+ width: 100%;
+ }
+
+ .best_items > li {
+ flex: 1;
+ }
+
.all_items {
display: grid;
grid-template-columns: repeat(5, 1fr);
@@ -58,25 +86,6 @@ export const ItemsStyle = styled.div`
}
}
- .best_item .product_img {
- width: ${pxToRem(282)};
- height: ${pxToRem(282)};
-
- ${media.tablet} {
- width: ${pxToRem(343)};
- height: ${pxToRem(343)};
- }
- }
-
- .all_items .product_img {
- width: ${pxToRem(221)};
- height: ${pxToRem(221)};
- ${media.mobile} {
- width: ${pxToRem(168)};
- height: ${pxToRem(168)};
- }
- }
-
${media.tablet} {
max-width: ${pxToRem(696)};
}
diff --git a/src/styles/GlobalStyle.js b/src/styles/GlobalStyle.js
index 2d70cf8c..5e956316 100644
--- a/src/styles/GlobalStyle.js
+++ b/src/styles/GlobalStyle.js
@@ -1,3 +1,4 @@
+import { media } from "@/styles/commonStyle";
import { pxToRem } from "@/utils/pxToRem";
import { createGlobalStyle } from "styled-components";
@@ -8,35 +9,6 @@ export const GlobalStyle = createGlobalStyle`
font-family: "Pretendard", sans-serif;
}
- :root {
- /* 색상 */
- --primary-100: #3692ff;
- --primary-200: #1967d6;
- --primary-300: #1251aa;
- --gray-50: #f9fafb;
- --gray-100: #f3f4f6;
- --gray-200: #e5e7eb;
- --gray-400: #9ca3af;
- --gray-500: #6b7280;
- --gray-600: #4b5563;
- --gray-700: #374151;
- --gray-800: #1f2937;
- --gray-900: #111827;
- --error-red: #f74747;
-
- /* border-radius */
- --border-8: ${pxToRem(8)};
- --border-10: ${pxToRem(10)};
- --border-16: ${pxToRem(16)};
-
- }
-
- /* pc기준 */
- .width_container {
- max-width: 70rem;
- margin: 0 auto;
- }
-
a,
.btn {
color: var(--gray-100);
diff --git a/src/styles/commomStyle.js b/src/styles/commonStyle.js
similarity index 89%
rename from src/styles/commomStyle.js
rename to src/styles/commonStyle.js
index a6ff56a6..5ab5c111 100644
--- a/src/styles/commomStyle.js
+++ b/src/styles/commonStyle.js
@@ -1,10 +1,16 @@
-import { css } from "styled-components";
+import { pxToRem } from "@/utils/pxToRem";
+import styled, { css } from "styled-components";
// pc, tablet, mobie
+export const breakpoints = {
+ pc: 1200,
+ tablet: 768,
+ mobile: 375,
+};
+
export const media = {
- pc: "@media (min-width: 1200px)",
- tablet: "@media (min-width: 768px) and (max-width: 1199px)",
- mobile: "@media (min-width: 375px) and (max-width: 767px)",
+ tablet: `@media (max-width: ${breakpoints.pc - 1}px)`,
+ mobile: `@media (max-width: ${breakpoints.tablet - 1}px)`,
};
//공통 스타일
@@ -18,6 +24,17 @@ export const fullSize = css`
height: 100%;
`;
+export const WidthContainer = styled.div`
+ max-width: 70rem;
+ margin: 0 auto;
+ ${media.tablet} {
+ max-width: ${pxToRem(696)};
+ }
+ ${media.mobile} {
+ max-width: ${pxToRem(344)};
+ }
+`;
+
//폰트 스타일
export const textStyles = {
"text-3xl-bold": css`
diff --git a/src/styles/variables.css b/src/styles/variables.css
new file mode 100644
index 00000000..478c6c93
--- /dev/null
+++ b/src/styles/variables.css
@@ -0,0 +1,21 @@
+:root {
+ /* 색상 */
+ --primary-100: #3692ff;
+ --primary-200: #1967d6;
+ --primary-300: #1251aa;
+ --gray-50: #f9fafb;
+ --gray-100: #f3f4f6;
+ --gray-200: #e5e7eb;
+ --gray-400: #9ca3af;
+ --gray-500: #6b7280;
+ --gray-600: #4b5563;
+ --gray-700: #374151;
+ --gray-800: #1f2937;
+ --gray-900: #111827;
+ --error-red: #f74747;
+
+ /* border-radius */
+ --border-8: 0.5rem;
+ --border-10: 0.625rem;
+ --border-16: 1rem;
+}
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 00000000..e7baf44c
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,9 @@
+///
+
+declare module "*.svg" {
+ import * as React from "react";
+ const Component: React.FunctionComponent<
+ React.SVGProps & { title?: string }
+ >;
+ export default Component;
+}
diff --git a/vite.config.js b/vite.config.js
index 77c4c1c2..c64e5843 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,9 +1,16 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
+import svgr from "vite-plugin-svgr";
export default defineConfig({
- plugins: [react()],
+ plugins: [
+ react(),
+ svgr({
+ exportAsDefault: true, // 기본 export를 React 컴포넌트로
+ include: "**/*.svg", // 모든 .svg 파일 적용
+ }),
+ ],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),