diff --git a/src/components/AllProductArea/AllProductArea.js b/src/components/AllProductArea/AllProductArea.js deleted file mode 100644 index d8c44bf1..00000000 --- a/src/components/AllProductArea/AllProductArea.js +++ /dev/null @@ -1,135 +0,0 @@ -import { useEffect, useState } from "react"; -import { Link } from "react-router-dom"; -import Pagination from "../Pagination/Pagination"; -import ProductList from "../ProductList/ProductList"; -import { getData } from "../../data/api"; -import styles from "./AllProductArea.module.scss"; -import { getItemCount } from "../../utils/getItemCount"; - -const SORT_TYPE = { - recent: "최신순", - favorite: "좋아요순", -}; -const SortDropdown = ({ orderBy, setOrderBy }) => { - const [sortOpen, setSortOpen] = useState(false); - const handleClickSort = () => setSortOpen(!sortOpen); - const handleSelectSort = (sort) => { - setOrderBy(sort); - setSortOpen(!sortOpen); - }; - - return ( - <> - - {sortOpen && ( -
- -
- )} - - ); -}; - -const ITEM_COUNT = { - WEB: 10, - TABLET: 6, - MOBILE: 4, -}; - -const INIT_PAGE_SIZE = getItemCount(ITEM_COUNT); - -const AllProductArea = () => { - const [currentPage, setCurrentPage] = useState(1); - const [orderBy, setOrderBy] = useState("recent"); - const [pageSize, setPageSize] = useState(INIT_PAGE_SIZE); - const [productList, setProductList] = useState([]); - const [totalCount, setTotalCount] = useState(0); - - // 요구 정의서 - // 1. orderby="recent", 10가지 상품을 전체 상품 리스트에 렌더링 - // 2. 반응형에 따라 웹에선 10, 타블렛에선 6, 모바일에선 4 보여주기 (미디어 쿼리 사용하기) - // 3. 전체 상품에서 드롭다운으로 최신순/좋아요순 정렬 기능 추가 - // 4. [심화] 페이지네이션 기능 구현 - - const getProductList = async (options) => { - try { - const data = await getData(options); - if (!data) return; - setProductList(data.list); - setTotalCount(data.totalCount); - } catch (error) { - console.error(error); - } - }; - - const updatePageSize = () => { - const itemCount = getItemCount(ITEM_COUNT); - setPageSize(itemCount); - }; - - useEffect(() => { - updatePageSize(); - window.addEventListener("resize", updatePageSize); - - return () => { - window.removeEventListener("resize", updatePageSize); - }; - }, []); - - useEffect(() => { - getProductList({ - page: currentPage, - pageSize: pageSize, - orderBy: orderBy, - }); - }, [currentPage, pageSize, orderBy]); - - return ( - <> -
-

전체 상품

-
-
- -
-
- - 상품 등록하기 - -
- -
-
-
- - -
- - ); -}; - -export default AllProductArea; diff --git a/src/components/AllProductArea/AllProductArea.module.scss b/src/components/AllProductArea/AllProductArea.module.scss deleted file mode 100644 index a00dfaa9..00000000 --- a/src/components/AllProductArea/AllProductArea.module.scss +++ /dev/null @@ -1,151 +0,0 @@ -@use "../../styles/variables" as var; -@use "../../styles/mixin" as mixin; - -.allProductArea { - &__title { - font-size: 20px; - font-weight: 700; - color: var.$gray900; - } - - &__utils { - display: flex; - align-items: center; - gap: 12px; - - @include mixin.mobile { - flex-wrap: wrap; - gap: 8px 14px; - } - - .utils { - $height: 42px; - - &__searchBox { - width: 325px; - margin-left: auto; - - @include mixin.tablet { - width: 242px; - } - - @include mixin.mobile { - margin: 0; - width: calc(100% - 56px); - order: 3; - } - - input { - display: block; - width: 100%; - height: $height; - padding: 0 12px 0 44px; - background: var.$gray100 - url("../../assets/images/icons/ic_search.svg") 16px center no-repeat; - border-radius: 12px; - } - } - - &__productAdd { - display: block; - width: 132px; - line-height: $height; - text-align: center; - font-size: 16px; - font-weight: 600; - color: var.$gray100; - background: var.$primary-color; - border-radius: 8px; - - @include mixin.mobile { - margin-left: auto; - } - } - - &__sortSelectBox { - position: relative; - width: 130px; - - @include mixin.mobile { - width: auto; - order: 4; - } - - .sortSelectBox { - &__current { - display: block; - width: 100%; - padding: 0 42px 0 20px; - height: $height; - border: 1px solid var.$gray200; - font-size: 16px; - text-align: left; - color: var.$gray800; - background: url("../../assets/images/icons/ic_select_arrow.svg") - right 20px center no-repeat; - border-radius: 12px; - - @include mixin.mobile { - width: $height; - padding: 0; - background: url("../../assets/images/icons/ic_sort.svg") center - center no-repeat; - - span { - display: none; - } - } - } - - &__list { - position: absolute; - top: 100%; - right: 0; - width: 130px; - margin-top: 8px; - z-index: 1; - overflow: hidden; - - ul { - border: 1px solid var.$gray200; - background: var.$white; - border-radius: 12px; - transform: translateY(-100%); - animation: showSortList 0.3s ease forwards; - - @keyframes showSortList { - 100% { - transform: translateY(0); - } - } - - li { - border-bottom: 1px solid var.$gray200; - - &:last-of-type { - border-bottom: none; - } - - button { - display: block; - width: 100%; - height: $height; - font-size: 16px; - color: var.$gray800; - } - } - } - } - } - } - } - } - - &__content { - margin-top: 24px; - - @include mixin.mobile { - margin-top: 16px; - } - } -} diff --git a/src/components/AuthFormInput/AuthFormInput.js b/src/components/AuthFormInput/AuthFormInput.js new file mode 100644 index 00000000..099f5434 --- /dev/null +++ b/src/components/AuthFormInput/AuthFormInput.js @@ -0,0 +1,49 @@ +import React from "react"; +import { getAuthValidClassName } from "../../utils/authUtils"; +import PasswordInput from "../PasswordInput/PasswordInput"; +import Input from "../Input/Input"; + +const AuthFormInput = ({ + label, + type, + name, + value, + onChange, + placeholder, + validInfo, +}) => { + const handleChangeValue = (e) => { + onChange(e.target.value); + }; + + const hasError = !validInfo.isValid; + + return ( +
+ + {type === "password" ? ( + + ) : ( + + )} + {hasError &&

{validInfo.msg}

} +
+ ); +}; + +export default AuthFormInput; diff --git a/src/components/Input/Input.js b/src/components/Input/Input.js new file mode 100644 index 00000000..4fdfd5ba --- /dev/null +++ b/src/components/Input/Input.js @@ -0,0 +1,17 @@ +import styles from "./Input.module.scss"; + +const Input = ({ type, name, value, onChange, placeholder, className }) => { + return ( + + ); +}; + +export default Input; diff --git a/src/components/Input/Input.module.scss b/src/components/Input/Input.module.scss new file mode 100644 index 00000000..77990b05 --- /dev/null +++ b/src/components/Input/Input.module.scss @@ -0,0 +1,5 @@ +@use "../../styles/mixin" as mixin; + +.input { + @include mixin.defaultInput; +} diff --git a/src/components/Pagination/Pagination.js b/src/components/Pagination/Pagination.js index f93c87ba..d49cb24f 100644 --- a/src/components/Pagination/Pagination.js +++ b/src/components/Pagination/Pagination.js @@ -1,34 +1,33 @@ -import { useCallback, useMemo } from "react"; +import { useEffect, useState } from "react"; import arrow from "../../assets/images/icons/ic_pagination_arrow.svg"; import styles from "./Pagination.module.scss"; const LIMIT = 5; const Pagination = ({ pageSize, totalCount, currentPage, setCurrentPage }) => { - // 페이지네이션 5개(LIMIT)로 끊어서 2차원 배열로 생성 - const calcPager = useMemo(() => { - if (!totalCount) return; - - const totalPager = Math.ceil(totalCount / pageSize); + const [currentGroup, setCurrentGroup] = useState([]); + const [totalPager, setTotalPager] = useState(0); - const pagerArr = []; - let pagerCounter = 0; - for (let i = 0; i < totalPager; i++) { - if (!pagerArr[pagerCounter]) pagerArr[pagerCounter] = []; - pagerArr[pagerCounter].push(i + 1); - if (pagerArr[pagerCounter].length === LIMIT) pagerCounter++; - } - - return { pagerArr, totalPager }; - }, [pageSize, totalCount]); + useEffect(() => { + if (!totalCount) return; - // currentPage 값이 있는 배열 반환 - const getCurrentGroup = useCallback(() => { - if (!calcPager) return; + const getTotalPager = Math.ceil(totalCount / pageSize); // 페이지네이션 총 개수 + setTotalPager(getTotalPager); - return calcPager.pagerArr.filter((item) => item.includes(currentPage))[0]; - }, [calcPager, currentPage]); + // 페이지네이션 5개(LIMIT)로 끊어서 2차원 배열로 생성 + const pagerArr = Array.from({ length: totalPager }).reduce( + (acc, cur, idx) => { + const groupIdx = Math.floor(idx / LIMIT); + if (!acc[groupIdx]) acc[groupIdx] = []; + acc[groupIdx].push(idx + 1); + return acc; + }, + [] + ); - const currentGroup = getCurrentGroup(); + // currentPage 값이 있는 배열 반환 + const currentGroup = pagerArr.find((item) => item.includes(currentPage)); + setCurrentGroup(currentGroup); + }, [currentPage, totalCount, pageSize, totalPager]); // prev 버튼 클릭 const handleClickPrev = () => { @@ -38,7 +37,7 @@ const Pagination = ({ pageSize, totalCount, currentPage, setCurrentPage }) => { // next 버튼 클릭 const handleClickNext = () => { - const totalPageNum = calcPager.totalPager; + const totalPageNum = totalPager; const changeCurrentPage = currentPage + 1 >= totalPageNum ? totalPageNum : currentPage + 1; setCurrentPage(changeCurrentPage); @@ -76,7 +75,7 @@ const Pagination = ({ pageSize, totalCount, currentPage, setCurrentPage }) => { type="button" className={`${styles["pagination__button"]} ${styles["pagination__button-next"]}`} onClick={handleClickNext} - disabled={currentPage === calcPager.totalPager} + disabled={currentPage === totalPager} > 다음으로 diff --git a/src/components/PasswordInput/PasswordInput.js b/src/components/PasswordInput/PasswordInput.js new file mode 100644 index 00000000..86be5956 --- /dev/null +++ b/src/components/PasswordInput/PasswordInput.js @@ -0,0 +1,44 @@ +import usePasswordToggle from "../../hooks/usePasswordToggle"; +import styles from "./PasswordInput.module.scss"; + +const PasswordInput = ({ + name, + value, + onChange, + placeholder, + className, + isToggle = true, +}) => { + const { toggle, handleClickToggle, toggleImg } = usePasswordToggle(); + return ( +
+ + {isToggle && ( + + )} +
+ ); +}; + +export default PasswordInput; diff --git a/src/components/PasswordInput/PasswordInput.module.scss b/src/components/PasswordInput/PasswordInput.module.scss new file mode 100644 index 00000000..f4449672 --- /dev/null +++ b/src/components/PasswordInput/PasswordInput.module.scss @@ -0,0 +1,20 @@ +@use "../../styles/mixin" as mixin; + +.password { + &-box { + position: relative; + } + + &-input { + @include mixin.defaultInput; + padding-right: 60px; + } + + &__toggle-btn { + position: absolute; + top: 0; + right: 12px; + height: 100%; + padding: 0 12px; + } +} diff --git a/src/components/ProductList/ProductList.js b/src/components/ProductList/ProductList.js index 65f1ce12..15f5b11b 100644 --- a/src/components/ProductList/ProductList.js +++ b/src/components/ProductList/ProductList.js @@ -1,12 +1,14 @@ import ProductItem from "../ProductItem/ProductItem"; import styles from "./ProductList.module.scss"; -const ProductList = ({ list, type = "all" }) => { +const ProductList = ({ list, type = "all", pageSize = null }) => { + const productList = pageSize ? list.slice(0, pageSize) : list; + return (