+ >
+ );
+}
+
+export default ItemInfo;
diff --git a/src/components/items/ItemInfo/ItemInfo.module.scss b/src/components/items/ItemInfo/ItemInfo.module.scss
new file mode 100644
index 000000000..6b1cdff8f
--- /dev/null
+++ b/src/components/items/ItemInfo/ItemInfo.module.scss
@@ -0,0 +1,53 @@
+@import '../../../styles/global.css';
+@import '../../../styles/global.scss';
+
+.item-info {
+ display: flex;
+ margin: 0 0 32px;
+ justify-content: space-between;
+ align-items: flex-start;
+
+ &__img {
+ width: 486px;
+ height: 486px;
+ }
+
+ &__content {
+ display: flex;
+ margin: 0 0 0 24px;
+ width: 690px;
+ height: 486px;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: flex-start;
+ }
+
+ &__title {
+ margin: 0 0 16px;
+ font-size: 24px;
+ font-weight: 600;
+ color: $gray-800;
+ }
+
+ &__price {
+ margin: 0 0 16px;
+ font-size: 40px;
+ font-weight: 600;
+ color: $gray-800;
+ }
+
+ &__content-title {
+ margin: 16px 0 0;
+ font-size: 14px;
+ font-weight: 500;
+ color: $gray-600;
+ }
+
+ &__desc-content {
+ margin: 8px 0 0;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 140%;
+ color: $gray-800;
+ }
+}
diff --git a/src/components/items/SellingItems/SellingItems.jsx b/src/components/items/SellingItems/SellingItems.jsx
index 4b063a2bb..e3c385125 100644
--- a/src/components/items/SellingItems/SellingItems.jsx
+++ b/src/components/items/SellingItems/SellingItems.jsx
@@ -5,6 +5,7 @@ import { getProducts } from '../../../pages/api/Items';
import Item from '../Item/Item';
import PaginationBar from '../PaginationBar/PaginationBar';
import './SellingItems.css';
+import { useNavigate } from 'react-router-dom';
const getPageSize = () => {
const width = window.innerWidth;
@@ -31,6 +32,7 @@ function SellingItems() {
const [totalPageNum, setTotalPageNum] = useState();
// 에러
const [fetchingError, setfetchingError] = useState(null);
+ const navigate = useNavigate();
const handlePageChange = (pageNumber) => {
setPage(pageNumber);
@@ -49,6 +51,12 @@ function SellingItems() {
}
};
+ const handleClickItem = (e) => {
+ e.preventDefault();
+
+ navigate(`/items/${e.currentTarget.dataset.itemId}`);
+ };
+
const fetchItemList = async ({ order, page, pageSize, keyword }) => {
try {
setfetchingError(null);
@@ -130,7 +138,11 @@ function SellingItems() {
)}
diff --git a/src/core/ui/buttons/Button/Button.jsx b/src/core/ui/buttons/Button/Button.jsx
index 97763771a..cba8102f7 100644
--- a/src/core/ui/buttons/Button/Button.jsx
+++ b/src/core/ui/buttons/Button/Button.jsx
@@ -1,6 +1,14 @@
import { useEffect, useRef } from 'react';
+
import './Button.css';
-function Button({ text = '버튼', onClick = () => {}, isDisabled = false }) {
+function Button({
+ text = '버튼',
+ onClick = () => {},
+ isDisabled = false,
+ iconFront,
+ iconBack,
+ customBorderRound,
+}) {
const ref = useRef();
useEffect(() => {
@@ -10,7 +18,9 @@ function Button({ text = '버튼', onClick = () => {}, isDisabled = false }) {
return (
<>
>
);
diff --git a/src/core/ui/buttons/FavoriteButton/FavoriteButton.jsx b/src/core/ui/buttons/FavoriteButton/FavoriteButton.jsx
new file mode 100644
index 000000000..09417d2cf
--- /dev/null
+++ b/src/core/ui/buttons/FavoriteButton/FavoriteButton.jsx
@@ -0,0 +1,24 @@
+import { useState } from 'react';
+import { ReactComponent as IconHeart } from '../../../../assets/images/icons/ic_heart.svg';
+import styles from './FavoriteButton.module.scss';
+
+function FavoriteButton({ initCount = 0 }) {
+ const [count, setCount] = useState(initCount);
+
+ const handleClick = () => {
+ setCount(count + 1);
+ };
+
+ return (
+ <>
+
+ >
+ );
+}
+
+export default FavoriteButton;
diff --git a/src/core/ui/buttons/FavoriteButton/FavoriteButton.module.scss b/src/core/ui/buttons/FavoriteButton/FavoriteButton.module.scss
new file mode 100644
index 000000000..fccff7064
--- /dev/null
+++ b/src/core/ui/buttons/FavoriteButton/FavoriteButton.module.scss
@@ -0,0 +1,20 @@
+@import '../../../../styles/global.scss';
+
+.btn {
+ border: 1px solid $gray-200;
+ border-radius: 35px;
+
+ &__wrapper {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 4px 12px;
+ }
+
+ &__count {
+ margin: 0 0 0 5px;
+ font-size: 16px;
+ font-weight: 500;
+ color: $gray-800;
+ }
+}
diff --git a/src/core/ui/cards/ImageCard/ImageCard.jsx b/src/core/ui/cards/ImageCard/ImageCard.jsx
new file mode 100644
index 000000000..5932de1cb
--- /dev/null
+++ b/src/core/ui/cards/ImageCard/ImageCard.jsx
@@ -0,0 +1,29 @@
+import styles from './ImageCard.module.scss';
+import errImg from '../../../../assets/images/market/img_default.png';
+
+function ImageCard({
+ imageSrc,
+ ImgDeafault,
+ isRoundEdged = false,
+ isRound = false,
+}) {
+ return (
+ <>
+
+
{
+ e.target.src = errImg;
+ }}
+ />
+
+ >
+ );
+}
+
+export default ImageCard;
diff --git a/src/core/ui/cards/ImageCard/ImageCard.module.scss b/src/core/ui/cards/ImageCard/ImageCard.module.scss
new file mode 100644
index 000000000..2b3128186
--- /dev/null
+++ b/src/core/ui/cards/ImageCard/ImageCard.module.scss
@@ -0,0 +1,17 @@
+.wrapper {
+ width: 100%;
+ height: 100%;
+}
+
+.photo__img {
+ width: 100%;
+ height: 100%;
+}
+
+.photo__img--round16 {
+ border-radius: 16px;
+}
+
+.photo__img--round50percent {
+ border-radius: 50%;
+}
diff --git a/src/core/ui/comments/CommentItem/CommentItem.jsx b/src/core/ui/comments/CommentItem/CommentItem.jsx
new file mode 100644
index 000000000..a92d5cd85
--- /dev/null
+++ b/src/core/ui/comments/CommentItem/CommentItem.jsx
@@ -0,0 +1,32 @@
+import ImageCard from '../../cards/ImageCard/ImageCard';
+import styles from './CommentItem.module.scss';
+
+export default function CommentItem({
+ profileImageSrc,
+ nickname = 'nickname',
+ content = 'content',
+ updatedAt = '0시간 전',
+}) {
+ return (
+ <>
+
+
{content}
+
+
+
+
+
+
{nickname}
+
{updatedAt}
+
+
+
+ >
+ );
+}
diff --git a/src/core/ui/comments/CommentItem/CommentItem.module.scss b/src/core/ui/comments/CommentItem/CommentItem.module.scss
new file mode 100644
index 000000000..f9c1c6cff
--- /dev/null
+++ b/src/core/ui/comments/CommentItem/CommentItem.module.scss
@@ -0,0 +1,44 @@
+@import '../../../../styles/global.scss';
+
+.comment {
+ display: flex;
+ width: 100%;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-start;
+
+ &__content {
+ width: 100%;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 140%;
+ color: $gray-800;
+ }
+
+ &__wrapper-info {
+ display: flex;
+ margin: 24px 0 0;
+ }
+
+ &__avatar {
+ width: 40px;
+ height: 40px;
+ }
+
+ &__wrapper-nickname {
+ margin: 0 0 0 8px;
+ }
+
+ &__nickname {
+ margin: 0 0 4px;
+ font-size: 14px;
+ font-weight: 400;
+ color: $gray-600;
+ }
+
+ &__updated-at {
+ font-size: 12px;
+ font-weight: 400;
+ color: $gray-400;
+ }
+}
diff --git a/src/core/ui/comments/CommentList/CommentList.jsx b/src/core/ui/comments/CommentList/CommentList.jsx
new file mode 100644
index 000000000..05bfe9a99
--- /dev/null
+++ b/src/core/ui/comments/CommentList/CommentList.jsx
@@ -0,0 +1,42 @@
+import HorizontalLine from '../../lines/HorizontalLine/HorizontalLine';
+import CommentItem from '../CommentItem/CommentItem';
+import styles from './CommentList.module.scss';
+
+export default function CommentList({
+ initCommentCount = 3,
+ comments = [],
+ emptyIcon,
+ emptyMessage = '',
+}) {
+ return (
+ <>
+
+ {comments.length > 0 &&
+ comments.map((comment) => (
+
+ ))}
+ {comments.length < 1 && (
+
+ {emptyIcon}
+
+ {emptyMessage}
+
+
+ )}
+
+ >
+ );
+}
diff --git a/src/core/ui/comments/CommentList/CommentList.module.scss b/src/core/ui/comments/CommentList/CommentList.module.scss
new file mode 100644
index 000000000..17e624149
--- /dev/null
+++ b/src/core/ui/comments/CommentList/CommentList.module.scss
@@ -0,0 +1,32 @@
+@import '../../../../styles/global.scss';
+
+.list {
+ margin: 24px 0 0;
+
+ &__wrapper-item {
+ margin: 0 0 24px;
+ }
+
+ &__item {
+ margin: 0 0 24px;
+ }
+
+ &__item:last-child {
+ margin: 0;
+ }
+
+ &__wrapper-empty {
+ display: flex;
+ margin: 0;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ }
+
+ &__empty-message {
+ font-size: 14px;
+ font-weight: 200;
+ line-height: 24px;
+ color: $gray-400;
+ }
+}
diff --git a/src/core/ui/inputs/SimpleInput/SimpleInput.jsx b/src/core/ui/inputs/SimpleInput/SimpleInput.jsx
new file mode 100644
index 000000000..37b8dd6fb
--- /dev/null
+++ b/src/core/ui/inputs/SimpleInput/SimpleInput.jsx
@@ -0,0 +1,23 @@
+import styles from './SimpleInput.module.scss';
+
+export default function SimpleInput({
+ lable = '',
+ name = '',
+ type = 'text',
+ placeholder = '',
+ isLabelVisible = true,
+}) {
+ return (
+ <>
+
+ {isLabelVisible && }
+
+
+ >
+ );
+}
diff --git a/src/core/ui/inputs/SimpleInput/SimpleInput.module.scss b/src/core/ui/inputs/SimpleInput/SimpleInput.module.scss
new file mode 100644
index 000000000..84852a6ba
--- /dev/null
+++ b/src/core/ui/inputs/SimpleInput/SimpleInput.module.scss
@@ -0,0 +1,35 @@
+@import '../../../../styles/global.scss';
+
+.wrapper-input {
+ display: flex;
+ width: 100%;
+ justify-content: center;
+ align-items: flex-start;
+ flex-direction: column;
+}
+
+.lable {
+ margin: 0 0 16px;
+ font-size: 16px;
+ font-weight: 600;
+ color: $gray-900;
+}
+
+.input {
+ display: flex;
+ align-items: flex-start;
+ padding: 16px 24px;
+ width: 100%;
+ height: 104px;
+ border-radius: 12px;
+ background-color: $gray-50;
+ border: none;
+
+ &::placeholder,
+ &::-webkit-input-placeholder,
+ &:-moz-placeholder,
+ &::-moz-placeholder,
+ &:-ms-input-placeholder {
+ color: blue;
+ }
+}
diff --git a/src/core/ui/lines/HorizontalLine/HorizontalLine.jsx b/src/core/ui/lines/HorizontalLine/HorizontalLine.jsx
new file mode 100644
index 000000000..b9d6c2d08
--- /dev/null
+++ b/src/core/ui/lines/HorizontalLine/HorizontalLine.jsx
@@ -0,0 +1,11 @@
+import styles from './HorizontalLine.module.scss';
+
+function HorizontalLine() {
+ return (
+ <>
+
+ >
+ );
+}
+
+export default HorizontalLine;
diff --git a/src/core/ui/lines/HorizontalLine/HorizontalLine.module.scss b/src/core/ui/lines/HorizontalLine/HorizontalLine.module.scss
new file mode 100644
index 000000000..c0dba0198
--- /dev/null
+++ b/src/core/ui/lines/HorizontalLine/HorizontalLine.module.scss
@@ -0,0 +1,7 @@
+@import '../../../../styles/global.scss';
+
+.line {
+ width: 100%;
+ height: 1px;
+ background-color: $gray-200;
+}
diff --git a/src/core/ui/tags/TagList/TagList.jsx b/src/core/ui/tags/TagList/TagList.jsx
new file mode 100644
index 000000000..f4c870116
--- /dev/null
+++ b/src/core/ui/tags/TagList/TagList.jsx
@@ -0,0 +1,23 @@
+import styles from './TagList.module.scss';
+
+function TagList({ tags = [], isSharpVisible }) {
+ return (
+ <>
+ {tags.length > 0 && (
+
+ {tags.map((tag, i) => (
+
+ {`${isSharpVisible ? '#' : ''}${tag}`}
+
+ ))}
+
+ )}
+ >
+ );
+}
+
+export default TagList;
diff --git a/src/core/ui/tags/TagList/TagList.module.scss b/src/core/ui/tags/TagList/TagList.module.scss
new file mode 100644
index 000000000..351bdf8bb
--- /dev/null
+++ b/src/core/ui/tags/TagList/TagList.module.scss
@@ -0,0 +1,19 @@
+@import '../../../../styles/global.scss';
+.tag-list {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-items: flex-start;
+ width: 100%;
+ margin: 0;
+}
+
+.tag-list__tag {
+ display: flex;
+ align-items: center;
+ margin: 12px 12px 0 0;
+ padding: 6px 16px;
+ border-radius: 26px;
+ color: $gray-800;
+ background-color: $gray-50;
+}
diff --git a/src/lib/items/hooks/useItem.js b/src/lib/items/hooks/useItem.js
new file mode 100644
index 000000000..499fea4ea
--- /dev/null
+++ b/src/lib/items/hooks/useItem.js
@@ -0,0 +1,48 @@
+import { useEffect, useState } from 'react';
+import { getProductById } from '../../../pages/api/Items';
+
+/**
+ *
+ * @param {number} productId
+ * @param {array} deps
+ * @returns [imgSrc, title, price, desc, tags, favoriteCount]
+ */
+export function useItem(productId = 0, deps = []) {
+ const [imgSrc, setImgSrc] = useState('');
+ const [title, setTitle] = useState('');
+ const [price, setPrice] = useState(0);
+ const [desc, setDesc] = useState('');
+ const [tags, setTags] = useState([]);
+ const [isFavorite, setIsFavorite] = useState(false);
+ const [favoriteCount, setFavoriteCount] = useState(0);
+
+ const fetchProduct = async () => {
+ try {
+ const result = await getProductById(productId);
+ setImgSrc(result.images[0]);
+ setTitle(result.name);
+ setPrice(result.price);
+ setDesc(result.description);
+ setTags(result.tags);
+ setIsFavorite(result.isFavorite);
+ setFavoriteCount(result.favoriteCount);
+ } catch (error) {
+ // 디폴트 값
+ setImgSrc(
+ 'https://www.shutterstock.com/image-vector/default-ui-image-placeholder-wireframes-600nw-1037719192.jpg'
+ );
+ setTitle('-');
+ setPrice(0);
+ setDesc('-');
+ setTags([]);
+ setIsFavorite(false);
+ setFavoriteCount(0);
+ }
+ };
+
+ useEffect(() => {
+ fetchProduct();
+ }, deps);
+
+ return [imgSrc, title, price, desc, tags, isFavorite, favoriteCount];
+}
diff --git a/src/lib/items/hooks/useItemComments.js b/src/lib/items/hooks/useItemComments.js
new file mode 100644
index 000000000..4acf5e4d2
--- /dev/null
+++ b/src/lib/items/hooks/useItemComments.js
@@ -0,0 +1,31 @@
+import { useEffect, useState } from 'react';
+import { getCommentsByProductId } from '../../../pages/api/Items';
+
+/**
+ *
+ * @param {number} productId
+ * @param {array} deps
+ * @returns [imgSrc, title, price, desc, tags, favoriteCount]
+ */
+export function useItemComments(productId = 0, limit = 1, cursor, deps = []) {
+ const [comments, setComments] = useState([]);
+ const [nextCursor, setNextCursor] = useState(null);
+
+ const fetchData = async () => {
+ try {
+ const result = await getCommentsByProductId(productId, limit, cursor);
+ setComments(result.list);
+ setNextCursor(result.nextCursor);
+ } catch (error) {
+ // 디폴트 값
+ setComments([]);
+ setNextCursor(null);
+ }
+ };
+
+ useEffect(() => {
+ fetchData();
+ }, deps);
+
+ return [comments, nextCursor];
+}
diff --git a/src/pages/addItems/AddItems.jsx b/src/pages/addItems/AddItems.jsx
index 50be45d80..c7f3cd560 100644
--- a/src/pages/addItems/AddItems.jsx
+++ b/src/pages/addItems/AddItems.jsx
@@ -98,7 +98,7 @@ function AddItems() {
type="text"
name="name"
value={values.name}
- placeholder="상품명을 적어주세요."
+ placeholder="상품명을 입력해주세요"
onChange={handleInputChange}
/>
@@ -110,7 +110,7 @@ function AddItems() {
className="input-form-add-item input-textarea"
name="desc"
value={values.desc}
- placeholder="상품 소개를 적어주세요."
+ placeholder="상품 소개를 입력해주세요"
onChange={handleInputChange}
/>