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 && (
-
-
- {Object.keys(SORT_TYPE).map((sort) => (
- -
-
-
- ))}
-
-
- )}
- >
- );
-};
-
-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 (
- {list.map((product) => (
+ {productList.map((product) => (
-
diff --git a/src/hooks/useAllValid.js b/src/hooks/useAllValid.js
deleted file mode 100644
index 59e6408d..00000000
--- a/src/hooks/useAllValid.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { useEffect, useState } from "react";
-
-const useAllValid = (valueValids) => {
- const [isAllValid, setIsAllValid] = useState(false);
-
- useEffect(() => {
- // auth 관련 페이지에서만 검증 요소가 전부 true인지 확인
- const allValid = Object.keys(valueValids).every((valid) => {
- return valueValids[valid].isValid;
- });
- setIsAllValid(allValid);
- }, [valueValids]);
-
- return isAllValid;
-};
-
-export default useAllValid;
diff --git a/src/hooks/usePagination.js b/src/hooks/usePagination.js
new file mode 100644
index 00000000..4f9a9174
--- /dev/null
+++ b/src/hooks/usePagination.js
@@ -0,0 +1,30 @@
+import { useCallback, useEffect, useState } from "react";
+import { getItemCount } from "../utils/getItemCount";
+
+const usePagination = (itemCount) => {
+ const INIT_PAGE_SIZE = getItemCount(itemCount || 10);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [pageSize, setPageSize] = useState(INIT_PAGE_SIZE);
+
+ const updatePageSize = useCallback(() => {
+ const changeItemCount = getItemCount(itemCount);
+ setPageSize(changeItemCount);
+ }, [itemCount]);
+
+ useEffect(() => {
+ updatePageSize();
+ window.addEventListener("resize", updatePageSize);
+
+ return () => {
+ window.removeEventListener("resize", updatePageSize);
+ };
+ }, [updatePageSize]);
+
+ return {
+ currentPage,
+ setCurrentPage,
+ pageSize,
+ };
+};
+
+export default usePagination;
diff --git a/src/pages/ItemsPage/ItemsPage.js b/src/pages/ItemsPage/ItemsPage.js
index a03dac54..7e074fb3 100644
--- a/src/pages/ItemsPage/ItemsPage.js
+++ b/src/pages/ItemsPage/ItemsPage.js
@@ -1,6 +1,6 @@
import styles from "./ItemsPage.module.scss";
-import AllProductArea from "../../components/AllProductArea/AllProductArea";
-import BestProductArea from "../../components/BestProductArea/BestProductArea";
+import AllProductArea from "./components/AllProductArea/AllProductArea";
+import BestProductArea from "./components/BestProductArea/BestProductArea";
const ItemsPage = () => {
return (
diff --git a/src/pages/ItemsPage/components/AllProductArea/AllProductArea.js b/src/pages/ItemsPage/components/AllProductArea/AllProductArea.js
new file mode 100644
index 00000000..2fbfa7b4
--- /dev/null
+++ b/src/pages/ItemsPage/components/AllProductArea/AllProductArea.js
@@ -0,0 +1,75 @@
+import { useEffect, useState } from "react";
+import { Link } from "react-router-dom";
+import Pagination from "../../../../components/Pagination/Pagination";
+import ProductList from "../../../../components/ProductList/ProductList";
+import { getData } from "../../../../data/api";
+import styles from "./AllProductArea.module.scss";
+import usePagination from "../../../../hooks/usePagination";
+import SortDropdown from "../SortDropdown/SortDropdown";
+
+const ITEM_COUNT = {
+ WEB: 10,
+ TABLET: 6,
+ MOBILE: 4,
+};
+
+const AllProductArea = () => {
+ const { currentPage, setCurrentPage, pageSize } = usePagination(ITEM_COUNT);
+ const [totalCount, setTotalCount] = useState(0);
+ const [orderBy, setOrderBy] = useState("recent");
+ const [productList, setProductList] = useState([]);
+
+ const getProductList = async (options) => {
+ try {
+ const data = await getData(options);
+ if (!data) return;
+ setProductList(data.list);
+ setTotalCount(data.totalCount);
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ useEffect(() => {
+ getProductList({
+ page: currentPage,
+ pageSize: pageSize,
+ orderBy: orderBy,
+ });
+ }, [currentPage, pageSize, orderBy]);
+
+ return (
+ <>
+
+
전체 상품
+
+
+
+
+ 상품 등록하기
+
+
+
+
+
+
+ >
+ );
+};
+
+export default AllProductArea;
diff --git a/src/pages/ItemsPage/components/AllProductArea/AllProductArea.module.scss b/src/pages/ItemsPage/components/AllProductArea/AllProductArea.module.scss
new file mode 100644
index 00000000..5f206e3b
--- /dev/null
+++ b/src/pages/ItemsPage/components/AllProductArea/AllProductArea.module.scss
@@ -0,0 +1,85 @@
+@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;
+ }
+ }
+ }
+ }
+
+ &__content {
+ margin-top: 24px;
+
+ @include mixin.mobile {
+ margin-top: 16px;
+ }
+ }
+}
diff --git a/src/components/BestProductArea/BestProductArea.js b/src/pages/ItemsPage/components/BestProductArea/BestProductArea.js
similarity index 50%
rename from src/components/BestProductArea/BestProductArea.js
rename to src/pages/ItemsPage/components/BestProductArea/BestProductArea.js
index 6759a4ba..9b959418 100644
--- a/src/components/BestProductArea/BestProductArea.js
+++ b/src/pages/ItemsPage/components/BestProductArea/BestProductArea.js
@@ -1,8 +1,8 @@
import { useEffect, useState } from "react";
-import ProductList from "../ProductList/ProductList";
-import { getData } from "../../data/api";
+import ProductList from "../../../../components/ProductList/ProductList";
+import { getData } from "../../../../data/api";
import styles from "./BestProductArea.module.scss";
-import { getItemCount } from "../../utils/getItemCount";
+import usePagination from "../../../../hooks/usePagination";
const INIT_PAGE_SIZE = 4;
const ITEM_COUNT = {
@@ -12,12 +12,8 @@ const ITEM_COUNT = {
};
const BestProductArea = () => {
+ const { pageSize } = usePagination(ITEM_COUNT);
const [bestList, setBestList] = useState([]);
- const [pageSize, setPageSize] = useState(INIT_PAGE_SIZE);
-
- // 요구 정의서
- // 1. orderby="favorite", 4가지 상품을 베스트 상품 리스트에 렌더링
- // 2. 반응형에 따라 웹에선 4, 타블렛에선 2, 모바일에선 1 보여주기 (미디어 쿼리 사용하기)
const getProductList = async (options) => {
try {
@@ -29,27 +25,15 @@ const BestProductArea = () => {
}
};
- const updatePageSize = () => {
- const itemCount = getItemCount(ITEM_COUNT);
- setPageSize(itemCount);
- };
-
useEffect(() => {
getProductList({ orderBy: "favorite", pageSize: INIT_PAGE_SIZE, page: 1 });
-
- updatePageSize();
- window.addEventListener("resize", updatePageSize);
-
- return () => {
- window.removeEventListener("resize", updatePageSize);
- };
}, []);
return (
<>
베스트 상품
>
);
diff --git a/src/components/BestProductArea/BestProductArea.module.scss b/src/pages/ItemsPage/components/BestProductArea/BestProductArea.module.scss
similarity index 77%
rename from src/components/BestProductArea/BestProductArea.module.scss
rename to src/pages/ItemsPage/components/BestProductArea/BestProductArea.module.scss
index 394a2702..7f1dd871 100644
--- a/src/components/BestProductArea/BestProductArea.module.scss
+++ b/src/pages/ItemsPage/components/BestProductArea/BestProductArea.module.scss
@@ -1,4 +1,4 @@
-@use "../../styles/variables" as var;
+@use "../../../../styles/variables" as var;
.bestProductArea {
&__title {
diff --git a/src/pages/ItemsPage/components/SortDropdown/SortDropdown.js b/src/pages/ItemsPage/components/SortDropdown/SortDropdown.js
new file mode 100644
index 00000000..af2403c4
--- /dev/null
+++ b/src/pages/ItemsPage/components/SortDropdown/SortDropdown.js
@@ -0,0 +1,43 @@
+import { useState } from "react";
+import styles from "./SortDropdown.module.scss";
+
+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 && (
+
+
+ {Object.keys(SORT_TYPE).map((sort) => (
+ -
+
+
+ ))}
+
+
+ )}
+ >
+ );
+};
+
+export default SortDropdown;
diff --git a/src/pages/ItemsPage/components/SortDropdown/SortDropdown.module.scss b/src/pages/ItemsPage/components/SortDropdown/SortDropdown.module.scss
new file mode 100644
index 00000000..3d643495
--- /dev/null
+++ b/src/pages/ItemsPage/components/SortDropdown/SortDropdown.module.scss
@@ -0,0 +1,71 @@
+@use "../../../../styles/variables" as var;
+@use "../../../../styles/mixin" as mixin;
+
+.sortSelectBox {
+ $height: 42px;
+
+ &__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;
+ }
+ }
+ }
+ }
+}
diff --git a/src/pages/LoginPage/LoginPage.js b/src/pages/LoginPage/LoginPage.js
index 0fd537c9..a6abf987 100644
--- a/src/pages/LoginPage/LoginPage.js
+++ b/src/pages/LoginPage/LoginPage.js
@@ -1,56 +1,59 @@
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
-import useAllValid from "../../hooks/useAllValid";
-import usePasswordToggle from "../../hooks/usePasswordToggle";
-import {
- checkValidEmail,
- checkValidPassword,
- getAuthValidClassName,
-} from "../../utils/authUtils";
+import { checkValidEmail, checkValidPassword } from "../../utils/authUtils";
import getLogo from "../../utils/getLogo";
import AuthSns from "../../components/AuthSns/AuthSns";
import AuthGuide from "../../components/AuthGuide/AuthGuide";
import "../../styles/auth.scss";
import styles from "./LoginPage.module.scss";
+import getIsAllValid from "../../utils/getIsAllValid";
+import AuthFormInput from "../../components/AuthFormInput/AuthFormInput";
-const INIT_VALUE = { email: "", password: "" };
const INIT_VALID = {
- email: {
- isValid: null,
- msg: "",
- },
- password: {
- isValid: null,
- msg: "",
- },
-};
-
-const VALIDATOR = {
- email: checkValidEmail,
- password: checkValidPassword,
+ isValid: null,
+ msg: "",
};
const LoginPage = () => {
const nav = useNavigate();
- const [userValues, setUserValues] = useState(INIT_VALUE);
- const [valueValids, setValueValids] = useState(INIT_VALID);
- const pwToggle = usePasswordToggle();
- const isAllValid = useAllValid(valueValids);
+ const [userEmail, setUserEmail] = useState("");
+ const [userPassword, setUserPassword] = useState("");
+ const [validUserEmail, setValidUserEmail] = useState(INIT_VALID);
+ const [validUserPassword, setValidUserPassword] = useState(INIT_VALID);
+ const [isAllValid, setIsAllValid] = useState(false);
+
+ const getUserValidation = (name, value) => {
+ let validEmail = validUserEmail;
+ let validPassword = validUserPassword;
- const handleChangeUserValues = (e) => {
- setUserValues({ ...userValues, [e.target.name]: e.target.value });
+ switch (name) {
+ case "email": {
+ validEmail = checkValidEmail(value);
+ break;
+ }
+ case "password": {
+ validPassword = checkValidPassword(value);
+ break;
+ }
+ // no default
+ }
+
+ return {
+ validEmail,
+ validPassword,
+ };
};
const handleFocusOut = (e) => {
const { name, value } = e.target;
- // 검증할 요소인지 확인
- if (!VALIDATOR[name]) return;
+ // 인풋 검증
+ const { validEmail, validPassword } = getUserValidation(name, value);
+ setValidUserEmail(() => validEmail);
+ setValidUserPassword(() => validPassword);
- setValueValids(() => ({
- ...valueValids,
- [name]: VALIDATOR[name](value),
- }));
+ // 버튼 활성화 여부
+ setIsAllValid(() => getIsAllValid([validEmail, validPassword]));
};
const handleClickSubmit = (e) => {
@@ -73,60 +76,27 @@ const LoginPage = () => {