diff --git a/package-lock.json b/package-lock.json index fa8c604a..1a44ef75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.62.0", "react-router-dom": "^6.30.0", "react-scripts": "5.0.1", "sass": "^1.89.0", @@ -15308,6 +15309,21 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-hook-form": { + "version": "7.62.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz", + "integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index 2265d62e..f39c45b9 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.62.0", "react-router-dom": "^6.30.0", "react-scripts": "5.0.1", "sass": "^1.89.0", diff --git a/src/App.tsx b/src/App.tsx index fca54db3..d24328d9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,8 +2,9 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom"; import routes from "./routes"; import "./styles/style.scss"; import ToastContainer from "./components/Toast/ToastContainer"; -import { Global } from "@emotion/react"; +import { Global, ThemeProvider } from "@emotion/react"; import { globalStyle } from "./styles/globalStyle"; +import { theme } from "@styles/theme"; function App() { const router = createBrowserRouter(routes, { @@ -13,9 +14,11 @@ function App() { }); return ( <> - - - + + + + + ); } diff --git a/src/components/AuthFormInput/AuthFormInput.js b/src/components/AuthFormInput/AuthFormInput.js deleted file mode 100644 index 29cc97dd..00000000 --- a/src/components/AuthFormInput/AuthFormInput.js +++ /dev/null @@ -1,45 +0,0 @@ -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 hasError = validInfo.isValid === false; - - return ( -
- - {type === "password" ? ( - - ) : ( - - )} - {hasError &&

{validInfo.msg}

} -
- ); -}; - -export default AuthFormInput; diff --git a/src/components/AuthFormInput/AuthFormInput.tsx b/src/components/AuthFormInput/AuthFormInput.tsx new file mode 100644 index 00000000..9ce5bf75 --- /dev/null +++ b/src/components/AuthFormInput/AuthFormInput.tsx @@ -0,0 +1,31 @@ +import PasswordInput from "../PasswordInput/PasswordInput"; +import Input from "../Input/Input"; +import { forwardRef, InputHTMLAttributes } from "react"; +import { + AuthFormItem, + AuthFormLabel, + AuthFormErrorMsg, +} from "@styles/formStyles"; + +interface Props extends InputHTMLAttributes { + label: string; + errorMsg?: string; +} + +const AuthFormInput = forwardRef( + ({ label, errorMsg, ...props }, ref) => { + return ( + + {label} + {props.type === "password" ? ( + + ) : ( + + )} + {errorMsg && {errorMsg}} + + ); + } +); + +export default AuthFormInput; diff --git a/src/components/AuthGuide/AuthGuide.js b/src/components/AuthGuide/AuthGuide.js deleted file mode 100644 index f14ed6f3..00000000 --- a/src/components/AuthGuide/AuthGuide.js +++ /dev/null @@ -1,18 +0,0 @@ -import { Link } from "react-router-dom"; - -const AuthGuide = ({ guideTxt, linkTxt, linkUrl }) => { - return ( -
-

{guideTxt}

- - {linkTxt} - -
- ); -}; - -export default AuthGuide; diff --git a/src/components/AuthGuide/AuthGuide.tsx b/src/components/AuthGuide/AuthGuide.tsx new file mode 100644 index 00000000..52d9a0d0 --- /dev/null +++ b/src/components/AuthGuide/AuthGuide.tsx @@ -0,0 +1,39 @@ +import styled from "@emotion/styled/macro"; +import { theme } from "@styles/theme"; +import { Link } from "react-router-dom"; + +interface Props { + guideTxt: string; + linkTxt: string; + linkUrl: string; +} + +const AuthGuide = ({ guideTxt, linkTxt, linkUrl }: Props) => { + return ( + +

{guideTxt}

+ + {linkTxt} + +
+ ); +}; + +const AuthGuideBox = styled.div` + display: flex; + align-items: center; + justify-content: center; + margin-top: 24px; + font-size: 14px; + font-weight: 500; + gap: 5px; +`; + +const AuthGuideLink = styled(Link)` + text-decoration: underline; + text-underline-offset: 3px; + text-decoration-thickness: 2px; + color: ${theme.colors.primaryColor}; +`; + +export default AuthGuide; diff --git a/src/components/AuthSns/AuthSns.js b/src/components/AuthSns/AuthSns.tsx similarity index 58% rename from src/components/AuthSns/AuthSns.js rename to src/components/AuthSns/AuthSns.tsx index 983bbeb7..b3be70d1 100644 --- a/src/components/AuthSns/AuthSns.js +++ b/src/components/AuthSns/AuthSns.tsx @@ -1,12 +1,13 @@ import { Link } from "react-router-dom"; import snsGoogle from "../../assets/images/icons/ic_sns_google.svg"; import snsKakao from "../../assets/images/icons/ic_sns_kakao.svg"; +import styled from "@emotion/styled/macro"; const AuthSns = () => { return ( -
-

간편 로그인하기

-
    + + 간편 로그인하기 +
  • { 카카오톡 아이콘
  • -
-
+ + ); }; +const AuthSnsBox = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 24px; + padding: 16px 23px; + background-color: #e6f2ff; + border-radius: 8px; +`; + +const AuthSnsLabel = styled.h2` + display: block; + font-size: 16px; + font-weight: 500; +`; + +const AuthSnsList = styled.ul` + display: flex; + align-items: center; + gap: 16px; +`; + export default AuthSns; diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 5744a2b2..bd2469b2 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -1,15 +1,12 @@ import { ButtonHTMLAttributes, ReactNode } from "react"; import { ButtonStyle, ButtonStyleProps } from "./ButtonStyle"; -interface BaseButtonProps { - onClick?: () => void; +interface ButtonProps + extends ButtonHTMLAttributes, + ButtonStyleProps { children: ReactNode; } -type ButtonProps = ButtonHTMLAttributes & - BaseButtonProps & - ButtonStyleProps; - const Button = ({ onClick, children, ...props }: ButtonProps) => { return ( diff --git a/src/components/Button/ButtonStyle.ts b/src/components/Button/ButtonStyle.ts index 177d11fb..377003f0 100644 --- a/src/components/Button/ButtonStyle.ts +++ b/src/components/Button/ButtonStyle.ts @@ -2,34 +2,35 @@ import { css } from "@emotion/react"; import styled from "@emotion/styled/macro"; import { mq } from "../../styles/mixins"; import { Link } from "react-router-dom"; +import { theme } from "@styles/theme"; export interface ButtonStyleProps { - color?: keyof typeof ButtonColors; + variant?: keyof typeof ButtonStyles; size?: keyof typeof ButtonSize; round?: boolean; } -export const ButtonColors = { +export const ButtonStyles = { primary: css` color: #fff; - background-color: var(--btn-primary); + background-color: ${theme.btn.primary}; &:hover { - background-color: var(--btn-primary-hover); + background-color: ${theme.btn.hover}; } &:active { - background-color: var(--btn-primary-click); + background-color: ${theme.btn.click}; } &:disabled { - background-color: var(--btn-disabled); + background-color: ${theme.btn.disabled}; } `, white: css` - border: 1px solid var(--primary-color); - color: var(--primary-color); - background-color: var(--white); + border: 1px solid ${theme.colors.primaryColor}; + color: ${theme.colors.primaryColor}; + background-color: ${theme.colors.white}; `, custom: css``, }; @@ -69,7 +70,7 @@ export const ButtonBaseStyle = (props: ButtonStyleProps) => css` cursor: pointer; ${props.size ? ButtonSize[props.size] : ButtonSize["sm"]}; - ${props.color ? ButtonColors[props.color] : ButtonColors["primary"]}; + ${props.variant ? ButtonStyles[props.variant] : ButtonStyles["primary"]}; border-radius: ${props.round ? "40px" : "8px"}; `; diff --git a/src/components/Input/Input.js b/src/components/Input/Input.js deleted file mode 100644 index 3d85e01a..00000000 --- a/src/components/Input/Input.js +++ /dev/null @@ -1,30 +0,0 @@ -import styles from "./Input.module.scss"; - -const Input = ({ - type, - name, - value, - onChange, - placeholder, - className, - ...params -}) => { - const handleChangeValue = (e) => { - onChange(e.target.value); - }; - - return ( - - ); -}; - -export default Input; diff --git a/src/components/Input/Input.module.scss b/src/components/Input/Input.module.scss deleted file mode 100644 index 77990b05..00000000 --- a/src/components/Input/Input.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -@use "../../styles/mixin" as mixin; - -.input { - @include mixin.defaultInput; -} diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx new file mode 100644 index 00000000..cce44fec --- /dev/null +++ b/src/components/Input/Input.tsx @@ -0,0 +1,10 @@ +import { forwardRef, InputHTMLAttributes } from "react"; +import { InputStyle } from "@styles/formStyles"; + +interface Props extends InputHTMLAttributes {} + +const Input = forwardRef(({ ...props }, ref) => { + return ; +}); + +export default Input; diff --git a/src/components/PasswordInput/PasswordInput.js b/src/components/PasswordInput/PasswordInput.js deleted file mode 100644 index 16bd3c5c..00000000 --- a/src/components/PasswordInput/PasswordInput.js +++ /dev/null @@ -1,49 +0,0 @@ -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(); - - const handleChangeValue = (e) => { - onChange(e.target.value); - }; - - return ( -
- - {isToggle && ( - - )} -
- ); -}; - -export default PasswordInput; diff --git a/src/components/PasswordInput/PasswordInput.module.scss b/src/components/PasswordInput/PasswordInput.module.scss deleted file mode 100644 index f4449672..00000000 --- a/src/components/PasswordInput/PasswordInput.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -@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/PasswordInput/PasswordInput.tsx b/src/components/PasswordInput/PasswordInput.tsx new file mode 100644 index 00000000..bb23af8e --- /dev/null +++ b/src/components/PasswordInput/PasswordInput.tsx @@ -0,0 +1,52 @@ +/** @jsxImportSource @emotion/react */ +import { forwardRef, InputHTMLAttributes } from "react"; +import usePasswordToggle from "../../hooks/usePasswordToggle"; +import { InputStyle } from "@styles/formStyles"; +import styled from "@emotion/styled/macro"; + +interface Props extends InputHTMLAttributes { + placeholder?: string; + className?: string; + isToggle?: boolean; +} + +const PasswordInput = forwardRef( + ({ type, isToggle = true, ...props }, ref) => { + const { toggle, handleClickToggle, toggleImg } = usePasswordToggle(); + + return ( + + + {isToggle && ( + + 비밀번호 보기 아이콘 + + )} + + ); + } +); + +const PasswordBox = styled.div` + position: relative; +`; + +const PasswordToggleBtn = styled.button` + position: absolute; + top: 0; + right: 12px; + height: 100%; + padding: 0 12px; +`; + +export default PasswordInput; diff --git a/src/components/TagItem/TagItem.tsx b/src/components/TagItem/TagItem.tsx index 847949b2..4584b247 100644 --- a/src/components/TagItem/TagItem.tsx +++ b/src/components/TagItem/TagItem.tsx @@ -4,19 +4,15 @@ import { } from "@components/TagItem/TagItemStyle"; import { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react"; -interface TagItemBase { +interface TextProps extends HTMLAttributes { + type: "text"; + children: ReactNode; +} +interface ButtonProps extends ButtonHTMLAttributes { + type: "button"; + onClick: () => void; children: ReactNode; } - -type TextProps = HTMLAttributes & - TagItemBase & { - type: "text"; - }; -type ButtonProps = ButtonHTMLAttributes & - TagItemBase & { - type: "button"; - onClick: () => void; - }; type TagItemProps = TextProps | ButtonProps; diff --git a/src/components/TagItem/TagItemStyle.ts b/src/components/TagItem/TagItemStyle.ts index cf9057ae..81be4480 100644 --- a/src/components/TagItem/TagItemStyle.ts +++ b/src/components/TagItem/TagItemStyle.ts @@ -1,6 +1,7 @@ import { css } from "@emotion/react"; import styled from "@emotion/styled/macro"; import DeleteIcon from "@assets/images/icons/ic_delete.svg"; +import { theme } from "@styles/theme"; const TagItemBaseStyle = css` display: inline-flex; @@ -8,8 +9,8 @@ const TagItemBaseStyle = css` height: 36px; padding: 5px 16px; font-size: 16px; - color: var(--gray800); - background: var(--gray100); + color: ${theme.colors.gray800}; + background: ${theme.colors.gray100}; border-radius: 26px; &::before { diff --git a/src/data/api.ts b/src/data/api.ts index 8ce76626..b15c898d 100644 --- a/src/data/api.ts +++ b/src/data/api.ts @@ -1,12 +1,22 @@ -import { InquiryItemType, ProductItemType } from "types/productType"; +import { + InquiryListType, + ProductItemDetailType, + ProductListType, +} from "types/productType"; const BASE_URL = process.env.REACT_APP_API_BASE_URL; +interface ProductQueryProps { + page: number; + pageSize: number; + orderBy: "recent" | "favorite"; +} + export const getData = async ({ page = 1, pageSize = 10, orderBy = "recent", -}) => { +}: ProductQueryProps): Promise => { const query = `page=${page}&pageSize=${pageSize}&orderBy=${orderBy}`; const res = await fetch(`${BASE_URL}/products?${query}`); @@ -20,7 +30,7 @@ export const getData = async ({ export const getProductInfo = async ( productId: number -): Promise => { +): Promise => { const res = await fetch(`${BASE_URL}/products/${productId}`); if (!res.ok) { @@ -33,7 +43,7 @@ export const getProductInfo = async ( export const getProductInquiries = async ( productId: number -): Promise<{ nextCursor: number; list: InquiryItemType[] }> => { +): Promise => { const res = await fetch( `${BASE_URL}/products/${productId}/comments?limit=10` ); diff --git a/src/index.js b/src/index.tsx similarity index 66% rename from src/index.js rename to src/index.tsx index 8db5acb8..153cce06 100644 --- a/src/index.js +++ b/src/index.tsx @@ -2,7 +2,9 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; -const root = ReactDOM.createRoot(document.getElementById("root")); +const root = ReactDOM.createRoot( + document.getElementById("root") as HTMLElement +); root.render( diff --git a/src/layouts/Footer.js b/src/layouts/Footer.tsx similarity index 100% rename from src/layouts/Footer.js rename to src/layouts/Footer.tsx diff --git a/src/layouts/Header.js b/src/layouts/Header.tsx similarity index 100% rename from src/layouts/Header.js rename to src/layouts/Header.tsx diff --git a/src/layouts/Layout.js b/src/layouts/Layout.tsx similarity index 100% rename from src/layouts/Layout.js rename to src/layouts/Layout.tsx diff --git a/src/pages/AddItemPage/AddItemPage.js b/src/pages/AddItemPage/AddItemPage.js index f72ba23a..7ac6c26c 100644 --- a/src/pages/AddItemPage/AddItemPage.js +++ b/src/pages/AddItemPage/AddItemPage.js @@ -1,5 +1,5 @@ /** @jsxImportSource @emotion/react */ -import { useMemo, useState } from "react"; +import React, { useMemo, useState } from "react"; import Input from "../../components/Input/Input"; import styles from "./AddItemPage.module.scss"; import TextArea from "../../components/TextArea/TextArea"; @@ -94,7 +94,7 @@ const AddItemPage = () => { type={"text"} name={"title"} value={prdName} - onChange={setPrdName} + onChange={(e) => setPrdName(e.target.value)} placeholder={"상품명을 입력해주세요"} /> @@ -121,7 +121,7 @@ const AddItemPage = () => { type={"tel"} name={"price"} value={prdPrice} - onChange={handleChangePrice} + onChange={(e) => handleChangePrice(e.target.value)} placeholder={"판매 가격을 입력해주세요"} /> @@ -134,7 +134,7 @@ const AddItemPage = () => { type={"text"} name={"tag"} value={tagInput} - onChange={setTagInput} + onChange={(e) => setTagInput(e.target.value)} placeholder={"태그를 입력해주세요"} onKeyDown={handleAddTag} /> @@ -142,7 +142,8 @@ const AddItemPage = () => {
{tagList.map((tag, id) => ( { handleDeleteTag(id); }} diff --git a/src/pages/LoginPage/LoginPage.js b/src/pages/LoginPage/LoginPage.js deleted file mode 100644 index 5ff5978e..00000000 --- a/src/pages/LoginPage/LoginPage.js +++ /dev/null @@ -1,121 +0,0 @@ -import { useEffect, useState } from "react"; -import { Link, useNavigate } from "react-router-dom"; -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 AuthFormInput from "../../components/AuthFormInput/AuthFormInput"; -import { getIsAllValid } from "../../utils/getIsAllValid"; - -const INIT_VALID = { - isValid: null, - msg: "", -}; - -const LoginPage = () => { - const nav = useNavigate(); - 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; - - 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; - - // 인풋 검증 - const { validEmail, validPassword } = getUserValidation(name, value); - setValidUserEmail(() => validEmail); - setValidUserPassword(() => validPassword); - }; - - const handleClickSubmit = (e) => { - e.preventDefault(); - nav("/"); - }; - - useEffect(() => { - // 버튼 활성화 여부 - setIsAllValid(getIsAllValid([validUserEmail, validUserPassword])); - }, [validUserEmail, validUserPassword]); - - return ( -
-
-

- - 판다마켓 로고 이미지 - -

-
- {/* 이메일 */} - - - {/* 비밀번호 */} - - - - - - -
-
- ); -}; - -export default LoginPage; diff --git a/src/pages/LoginPage/LoginPage.module.scss b/src/pages/LoginPage/LoginPage.module.scss deleted file mode 100644 index 62fdafba..00000000 --- a/src/pages/LoginPage/LoginPage.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -@use "../../styles/mixin" as mixin; - -.loginPage { - padding: 231px 0; - - @include mixin.tablet { - padding: 190px 0; - } - - @include mixin.mobile { - padding: 80px 0; - } -} diff --git a/src/pages/LoginPage/LoginPage.tsx b/src/pages/LoginPage/LoginPage.tsx new file mode 100644 index 00000000..6a3d0ee5 --- /dev/null +++ b/src/pages/LoginPage/LoginPage.tsx @@ -0,0 +1,122 @@ +/** @jsxImportSource @emotion/react */ +import { Link } from "react-router-dom"; +import { getAuthValidStateClassName } from "../../utils/authUtils"; +import getLogo from "../../utils/getLogo"; +import AuthSns from "../../components/AuthSns/AuthSns"; +import AuthGuide from "../../components/AuthGuide/AuthGuide"; +import AuthFormInput from "../../components/AuthFormInput/AuthFormInput"; +import { SubmitHandler, useForm } from "react-hook-form"; +import { css } from "@emotion/react"; +import { AuthContainer, AuthForm, AuthLogoImage } from "@styles/formStyles"; +import Button from "@components/Button/Button"; +import styled from "@emotion/styled/macro"; +import { mq } from "@styles/mixins"; + +interface FormDataType { + email: string; + password: string; +} + +const LoginPage = () => { + const { + register, + handleSubmit, + formState: { errors, isValid, touchedFields }, + } = useForm({ mode: "onBlur" }); + + const handleSubmitFormData: SubmitHandler = (data) => { + console.log(data); + }; + + return ( + + +

+ + + +

+ + {/* 이메일 */} + + + {/* 비밀번호 */} + + + + + + +
+
+ ); +}; + +const LoginPageStyle = styled.div` + padding: 231px 0; + + ${mq["tablet"]} { + padding: 190px 0; + } + + ${mq["mobile"]} { + padding: 80px 0; + } +`; + +export default LoginPage; diff --git a/src/pages/MainPage/MainPage.js b/src/pages/MainPage/MainPage.tsx similarity index 100% rename from src/pages/MainPage/MainPage.js rename to src/pages/MainPage/MainPage.tsx diff --git a/src/pages/ProductDetailPage/components/DetailDropdown/DetailDropdownStyle.ts b/src/pages/ProductDetailPage/components/DetailDropdown/DetailDropdownStyle.ts index 58d273a6..b64cfff4 100644 --- a/src/pages/ProductDetailPage/components/DetailDropdown/DetailDropdownStyle.ts +++ b/src/pages/ProductDetailPage/components/DetailDropdown/DetailDropdownStyle.ts @@ -1,9 +1,10 @@ import { css } from "@emotion/react"; import { mq } from "@styles/mixins"; +import { theme } from "@styles/theme"; export const DropdownListStyle = css` width: 139px; - border: 1px solid var(--gray300); + border: 1px solid ${theme.colors.gray300}; background: #fff; border-radius: 8px; z-index: 1; @@ -17,11 +18,11 @@ export const DropdownListStyle = css` export const DropdownItemStyle = css` width: 100%; padding: 12px 0 8px; - color: var(--gray500); + color: ${theme.colors.gray500}; transition: background-color 0.3s ease; &:hover { - background-color: var(--gray100); + background-color: ${theme.colors.gray100}; } ${mq["mobile"]} { diff --git a/src/pages/ProductDetailPage/components/ProductInfo/FavoriteButton.tsx b/src/pages/ProductDetailPage/components/ProductInfo/FavoriteButton.tsx index b1018425..7b808e59 100644 --- a/src/pages/ProductDetailPage/components/ProductInfo/FavoriteButton.tsx +++ b/src/pages/ProductDetailPage/components/ProductInfo/FavoriteButton.tsx @@ -1,12 +1,13 @@ import { css } from "@emotion/react"; import { ReactComponent as HearIcon } from "@assets/images/icons/ic_heart.svg"; -import { ProductItemType } from "types/productType"; +import { ProductItemDetailType } from "types/productType"; import styled from "@emotion/styled/macro"; import { mq } from "@styles/mixins"; import { ButtonHTMLAttributes } from "react"; +import { theme } from "@styles/theme"; type Props = ButtonHTMLAttributes & - Pick; + Pick; const FavoriteButton = ({ favoriteCount, isFavorite, ...props }: Props) => { return ( @@ -22,18 +23,18 @@ const activeHeartStyle = css` `; const inactiveHeartStyle = css` - stroke: var(--gray500); + stroke: ${theme.colors.gray500}; fill: transparent; `; const HeartButton = styled.button<{ isFavorite?: boolean }>` display: flex; align-items: center; - border: 1px solid var(--gray200); + border: 1px solid ${theme.colors.gray200}; padding: 4px 12px; gap: 4px; font-weight: 500; - color: var(--gray500); + color: ${theme.colors.gray500}; border-radius: 35px; path { diff --git a/src/pages/ProductDetailPage/components/ProductInfo/ProductInfoContent.tsx b/src/pages/ProductDetailPage/components/ProductInfo/ProductInfoContent.tsx index 2d374896..90e70e8d 100644 --- a/src/pages/ProductDetailPage/components/ProductInfo/ProductInfoContent.tsx +++ b/src/pages/ProductDetailPage/components/ProductInfo/ProductInfoContent.tsx @@ -3,15 +3,16 @@ import DetailDropdown from "@pages/ProductDetailPage/components/DetailDropdown/D import ProductInfoItem from "@pages/ProductDetailPage/components/ProductInfo/ProductInfoItem"; import UserProfile from "@pages/ProductDetailPage/components/UserProfile/UserProfile"; import { formatDate, formatPrice } from "@utils/formatters"; -import { ProductItemType } from "types/productType"; +import { ProductItemDetailType } from "types/productType"; import TagList from "@pages/ProductDetailPage/components/ProductInfo/TagList"; import FavoriteButton from "@pages/ProductDetailPage/components/ProductInfo/FavoriteButton"; import styled from "@emotion/styled/macro"; import { mq } from "@styles/mixins"; import { css } from "@emotion/react"; +import { theme } from "@styles/theme"; interface Props { - productInfo: ProductItemType; + productInfo: ProductItemDetailType; } const ProductInfoContent = ({ productInfo }: Props) => { @@ -62,7 +63,7 @@ const InfoHeader = styled.div` position: relative; margin-bottom: 24px; - border-bottom: 1px solid var(--gray200); + border-bottom: 1px solid ${theme.colors.gray200}; padding-bottom: 16px; ${mq["tablet"]} { @@ -73,7 +74,7 @@ const InfoHeader = styled.div` margin-bottom: 16px; font-size: 24px; font-weight: 600; - color: var(--gray800); + color: ${theme.colors.gray800}; line-height: 1.3; ${mq["tablet"]} { @@ -90,7 +91,7 @@ const InfoHeader = styled.div` display: block; font-size: 40px; font-weight: 600; - color: var(--gray800); + color: ${theme.colors.gray800}; line-height: 1.2; ${mq["tablet"]} { @@ -125,7 +126,7 @@ const PostInfo = styled.div` width: 1px; height: 34px; transform: translateY(-50%); - background: var(--gray200); + background: ${theme.colors.gray200}; content: ""; } } diff --git a/src/pages/ProductDetailPage/components/ProductInfo/ProductInfoItem.tsx b/src/pages/ProductDetailPage/components/ProductInfo/ProductInfoItem.tsx index ff133250..678db9d9 100644 --- a/src/pages/ProductDetailPage/components/ProductInfo/ProductInfoItem.tsx +++ b/src/pages/ProductDetailPage/components/ProductInfo/ProductInfoItem.tsx @@ -1,5 +1,6 @@ import styled from "@emotion/styled/macro"; import { mq } from "@styles/mixins"; +import { theme } from "@styles/theme"; import { ReactNode } from "react"; interface Props { @@ -22,7 +23,7 @@ const InfoItem = styled.div` .subject { margin-bottom: 16px; font-weight: 600; - color: var(--gray600); + color: ${theme.colors.gray600}; ${mq["tablet"]} { margin-bottom: 8px; @@ -31,7 +32,7 @@ const InfoItem = styled.div` } .desc { - color: var(--gray600); + color: ${theme.colors.gray600}; white-space: pre-line; } `; diff --git a/src/pages/ProductDetailPage/components/ProductInfo/index.tsx b/src/pages/ProductDetailPage/components/ProductInfo/index.tsx index ba6e37db..edf1a098 100644 --- a/src/pages/ProductDetailPage/components/ProductInfo/index.tsx +++ b/src/pages/ProductDetailPage/components/ProductInfo/index.tsx @@ -6,13 +6,15 @@ import { } from "@pages/ProductDetailPage/components/ProductInfo/indexStyle"; import { useParams } from "react-router-dom"; import { useEffect, useState } from "react"; -import { ProductItemType } from "types/productType"; +import { ProductItemDetailType } from "types/productType"; import ProductInfoContent from "@pages/ProductDetailPage/components/ProductInfo/ProductInfoContent"; import { getProductInfo } from "@data/api"; const ProductInfo = () => { const { productId } = useParams(); - const [productInfo, setProductInfo] = useState(null); + const [productInfo, setProductInfo] = useState( + null + ); useEffect(() => { const getProduct = async () => { diff --git a/src/pages/ProductDetailPage/components/ProductInfo/indexStyle.ts b/src/pages/ProductDetailPage/components/ProductInfo/indexStyle.ts index 90030af7..4ea3b2ac 100644 --- a/src/pages/ProductDetailPage/components/ProductInfo/indexStyle.ts +++ b/src/pages/ProductDetailPage/components/ProductInfo/indexStyle.ts @@ -1,12 +1,13 @@ import styled from "@emotion/styled/macro"; -import { mq } from "../../../../styles/mixins"; +import { mq } from "@styles/mixins"; +import { theme } from "@styles/theme"; export const DetailInfoBox = styled.div` display: flex; align-items: flex-start; gap: 24px; margin-bottom: 40px; - border-bottom: 1px solid var(--gray200); + border-bottom: 1px solid ${theme.colors.gray200}; padding-bottom: 40px; ${mq["tablet"]} { diff --git a/src/pages/ProductDetailPage/components/ProductInquiry/ProductInquiryEditor.tsx b/src/pages/ProductDetailPage/components/ProductInquiry/ProductInquiryEditor.tsx index 446b581f..180aff39 100644 --- a/src/pages/ProductDetailPage/components/ProductInquiry/ProductInquiryEditor.tsx +++ b/src/pages/ProductDetailPage/components/ProductInquiry/ProductInquiryEditor.tsx @@ -3,6 +3,7 @@ import Button from "@components/Button/Button"; import TextArea from "@components/TextArea/TextArea"; import { css } from "@emotion/react"; import InquiryUserProfile from "@pages/ProductDetailPage/components/ProductInquiry/InquiryUserProfile"; +import { theme } from "@styles/theme"; import { ChangeEvent, useState } from "react"; import { InquiryItemType } from "types/productType"; @@ -42,7 +43,7 @@ const InquiryEditor = ({
- - - -
-
- ); -}; - -export default SignupPage; diff --git a/src/pages/SignupPage/SignupPage.module.scss b/src/pages/SignupPage/SignupPage.module.scss deleted file mode 100644 index b921de0b..00000000 --- a/src/pages/SignupPage/SignupPage.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -@use "../../styles/mixin" as mixin; - -.signupPage { - padding: 60px 0; - - @include mixin.tablet { - padding: 48px 0; - } - - @include mixin.mobile { - padding: 24px 0; - } -} diff --git a/src/pages/SignupPage/SignupPage.tsx b/src/pages/SignupPage/SignupPage.tsx new file mode 100644 index 00000000..6bf88ea6 --- /dev/null +++ b/src/pages/SignupPage/SignupPage.tsx @@ -0,0 +1,173 @@ +/** @jsxImportSource @emotion/react */ +import { Link } from "react-router-dom"; +import { getAuthValidStateClassName } from "../../utils/authUtils"; +import getLogo from "../../utils/getLogo"; +import AuthSns from "../../components/AuthSns/AuthSns"; +import AuthGuide from "../../components/AuthGuide/AuthGuide"; +import AuthFormInput from "../../components/AuthFormInput/AuthFormInput"; +import { SubmitHandler, useForm } from "react-hook-form"; +import { css } from "@emotion/react"; +import { AuthContainer, AuthForm, AuthLogoImage } from "@styles/formStyles"; +import Button from "@components/Button/Button"; +import styled from "@emotion/styled/macro"; +import { mq } from "@styles/mixins"; + +interface FormDataType { + email: string; + nickname: string; + password: string; + passwordConfirm: string; +} + +const SignupPage = () => { + const { + register, + handleSubmit, + trigger, + getValues, + formState: { errors, isValid, touchedFields }, + } = useForm({ mode: "onBlur" }); + + const handleSubmitFormData: SubmitHandler = (data) => { + console.log(data); + }; + + return ( + + +

+ + + +

+ + {/* 이메일 */} + + + {/* 닉네임 */} + + + {/* 비밀번호 */} + {/* 최초 렌더링시에는 입력할 때 리렌더링이 발생하지 않음. */} + {/* watch 때문에 불필요한 리렌더링 발생 :: watch는 실시간으로 값을 추적하기 때문에 리렌더링이 발생할 수밖에 없는거 같다. 웬만해선 안쓰는게 좋을 듯... */} + {/* getValues는 리렌더링이 발생하지 않고, 실시간으로 값을 추적하지는 못하지만, 실행될 시점에 저장되어 있는 값을 가져옴. */} + { + // validate를 사용했으나, 간헐적으로 무한루프가 일어날 때가 있어서, onBlur로 변경. + const passwordConfirm = getValues("passwordConfirm"); + if (passwordConfirm) trigger("passwordConfirm"); + }, + })} + errorMsg={errors.password?.message} + css={css` + ${getAuthValidStateClassName( + touchedFields.password, + errors.password?.message + )} + `} + /> + + {/* 비밀번호 확인 */} + { + const password = getValues("password"); + if (value !== password) return "비밀번호가 일치하지 않습니다."; + return true; + }, + })} + errorMsg={errors.passwordConfirm?.message} + css={css` + ${getAuthValidStateClassName( + touchedFields.passwordConfirm, + errors.passwordConfirm?.message + )} + `} + /> + + + + + +
+
+ ); +}; + +const SignupPageStyle = styled.div` + padding: 60px 0; + + ${mq["tablet"]} { + padding: 48px 0; + } + + ${mq["mobile"]} { + padding: 24px 0; + } +`; + +export default SignupPage; diff --git a/src/store/toastStore.js b/src/store/toastStore.ts similarity index 64% rename from src/store/toastStore.js rename to src/store/toastStore.ts index 7347c8ae..9e8290ab 100644 --- a/src/store/toastStore.js +++ b/src/store/toastStore.ts @@ -1,12 +1,31 @@ import { create } from "zustand"; import { v4 as uuidv4 } from "uuid"; +interface ToastType { + id: string; + message: string; + delay: number; + duration: number; +} + +interface CreateToastType { + message: string; + delay?: number; + duration?: number; +} + +interface ToastStoreType { + toasts: ToastType[]; + createToast: (toast: CreateToastType) => void; + deleteToast: (id: string) => void; +} + const TOAST_DEFAULTS = { DELAY: 5000, //ms DURATION: 500, //ms }; -export const useToastStore = create((set) => ({ +export const useToastStore = create((set) => ({ toasts: [], createToast: (toast) => { if (!toast.message) { @@ -16,7 +35,7 @@ export const useToastStore = create((set) => ({ } const id = uuidv4(); - const newToast = { + const newToast: ToastType = { delay: TOAST_DEFAULTS.DELAY, duration: TOAST_DEFAULTS.DURATION, ...toast, diff --git a/src/styles/_layout.scss b/src/styles/_layout.scss index e16da7b7..4740ddc1 100644 --- a/src/styles/_layout.scss +++ b/src/styles/_layout.scss @@ -7,13 +7,6 @@ margin: 0 auto; } -#wrap { - display: flex; - flex-direction: column; - min-height: 100vh; - background: #fcfcfc; -} - // header #header { position: sticky; @@ -101,10 +94,6 @@ } } } -// container -#container { - flex: 1; -} // footer #footer { diff --git a/src/styles/auth.scss b/src/styles/auth.scss deleted file mode 100644 index 8393965b..00000000 --- a/src/styles/auth.scss +++ /dev/null @@ -1,110 +0,0 @@ -@use "variables" as var; -@use "mixin" as mixin; - -.auth { - &-container { - max-width: 640px; - width: calc(100% - 30px); - margin: 0 auto; - - @include mixin.mobile { - max-width: 400px; - } - - .logo { - img { - margin: 0 auto; - - @include mixin.mobile { - width: 198px; - } - } - } - } - - &-form { - margin-top: 40px; - - @include mixin.mobile { - margin-top: 24px; - } - - &__item { - margin-bottom: 24px; - - @include mixin.mobile { - font-size: 16px; - } - } - - &__label { - display: block; - margin-bottom: 16px; - font-size: 18px; - font-weight: 700; - color: var.$gray800; - - @include mixin.mobile { - font-size: 14px; - } - } - - &__submit-btn { - width: 100%; - } - - &__error-msg { - padding: 8px 16px 0; - font-size: 14px; - font-weight: 600; - color: var.$error; - } - } - - &-sns { - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 24px; - padding: 16px 23px; - background-color: #e6f2ff; - border-radius: 8px; - - &__label { - display: block; - font-size: 16px; - font-weight: 500; - } - - &__list { - display: flex; - align-items: center; - gap: 16px; - } - } - - &-guide { - display: flex; - align-items: center; - justify-content: center; - margin-top: 24px; - font-size: 14px; - font-weight: 500; - gap: 5px; - - &__link { - text-decoration: underline; - text-underline-offset: 3px; - text-decoration-thickness: 2px; - color: var.$primary-color; - } - } -} - -.isError { - border: 1px solid var.$error; -} - -.isPass { - border: 1px solid var.$primary-color; -} diff --git a/src/styles/fontStyles.ts b/src/styles/fontStyles.ts index 361b1845..2e604ce4 100644 --- a/src/styles/fontStyles.ts +++ b/src/styles/fontStyles.ts @@ -1,6 +1,5 @@ import { css } from "@emotion/react"; export const fontStyles = css` - /* Pretendard Variable */ @import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"); `; diff --git a/src/styles/formStyles.ts b/src/styles/formStyles.ts new file mode 100644 index 00000000..7a89ad1f --- /dev/null +++ b/src/styles/formStyles.ts @@ -0,0 +1,60 @@ +import styled from "@emotion/styled/macro"; +import { defaultInput, mq } from "@styles/mixins"; +import { theme } from "@styles/theme"; + +// common input style +export const InputStyle = styled.input` + ${defaultInput()} +`; + +// auth form - 회원가입 / 로그인 +export const AuthContainer = styled.div` + max-width: 640px; + width: calc(100% - 30px); + margin: 0 auto; + + ${mq["mobile"]} { + max-width: 400px; + } +`; + +export const AuthLogoImage = styled.img` + margin: 0 auto; + ${mq["mobile"]} { + width: 198px; + } +`; + +export const AuthForm = styled.form` + margin-top: 40px; + ${mq["mobile"]} { + margin-top: 24px; + } +`; + +export const AuthFormItem = styled.div` + margin-bottom: 24px; + + ${mq["mobile"]} { + font-size: 16px; + } +`; + +export const AuthFormLabel = styled.label` + display: block; + margin-bottom: 16px; + font-size: 18px; + font-weight: 700; + color: ${theme.colors.gray800}; + + ${mq["mobile"]} { + font-size: 14px; + } +`; + +export const AuthFormErrorMsg = styled.p` + padding: 8px 16px 0; + font-size: 14px; + font-weight: 600; + color: ${theme.colors.error}; +`; diff --git a/src/styles/globalStyle.ts b/src/styles/globalStyle.ts index fdeecee7..8362fc82 100644 --- a/src/styles/globalStyle.ts +++ b/src/styles/globalStyle.ts @@ -1,10 +1,8 @@ -import { css } from "@emotion/react"; +import { css, Theme } from "@emotion/react"; import { resetStyles } from "./resetStyles"; import { fontStyles } from "./fontStyles"; -import { variablesStyles } from "./variablesStyles"; -export const globalStyle = css` - ${variablesStyles} +export const globalStyle = (theme: Theme) => css` ${fontStyles} - ${resetStyles} + ${resetStyles(theme)} `; diff --git a/src/styles/mixins.ts b/src/styles/mixins.ts index 9f7a67f5..523015d1 100644 --- a/src/styles/mixins.ts +++ b/src/styles/mixins.ts @@ -1,4 +1,5 @@ import { css } from "@emotion/react"; +import { theme } from "@styles/theme"; const BREAKPOINTS = { tablet: 1199, @@ -29,7 +30,7 @@ export const defaultInput = (height: number = 56) => css` padding: 0 24px; font-size: 16px; font-weight: 400; - color: var(--gray800); - background: var(--gray100); + color: ${theme.colors.gray800}; + background: ${theme.colors.gray100}; border-radius: 12px; `; diff --git a/src/styles/resetStyles.ts b/src/styles/resetStyles.ts index 52e0fe34..53abc205 100644 --- a/src/styles/resetStyles.ts +++ b/src/styles/resetStyles.ts @@ -1,6 +1,6 @@ -import { css } from "@emotion/react"; +import { css, Theme } from "@emotion/react"; -export const resetStyles = css` +export const resetStyles = (theme: Theme) => css` * { margin: 0; padding: 0; @@ -12,8 +12,8 @@ export const resetStyles = css` html, body { - font-family: "Pretendard Variable", Pretendard, sans-serif; - color: #374151; + font-family: ${theme.font.family}; + color: ${theme.colors.gray700}; font-size: 16px; line-height: 1.4; } @@ -40,7 +40,7 @@ export const resetStyles = css` outline: none; &::placeholder { - color: var(--gray400); + color: ${theme.colors.gray400}; } } @@ -49,4 +49,16 @@ export const resetStyles = css` border: none; cursor: pointer; } + + #wrap { + display: flex; + flex-direction: column; + min-height: 100vh; + background: #fcfcfc; + } + + // container + #container { + flex: 1; + } `; diff --git a/src/styles/theme.ts b/src/styles/theme.ts new file mode 100644 index 00000000..353600dc --- /dev/null +++ b/src/styles/theme.ts @@ -0,0 +1,47 @@ +import "@emotion/react"; + +const primaryColor = "#3692ff"; +export const theme = { + colors: { + primaryColor: primaryColor, + primaryBg: "#cfe5ff", + white: "#fff", + gray50: "#f9fafb", + gray100: "#f3f4f6", + gray200: "#e5e7eb", + gray300: "#d1d5db", + gray400: "#9ca3af", + gray500: "#6b7280", + gray600: "#4b5563", + gray700: "#374151", + gray800: "#1f2937", + gray900: "#111827", + error: "#f74747", + }, + btn: { + primary: primaryColor, + hover: "#1967d6", + click: "#1251aa", + disabled: "#9ca3af", + }, + footer: { + bg: "#111827", + }, + responsive: { + tablet: "1199px", + mobile: "767px", + }, + font: { + family: '"Pretendard Variable", Pretendard, sans-serif', + }, +}; + +declare module "@emotion/react" { + export interface Theme { + colors: typeof theme.colors; + btn: typeof theme.btn; + footer: typeof theme.footer; + responsive: typeof theme.responsive; + font: typeof theme.font; + } +} diff --git a/src/styles/variablesStyles.ts b/src/styles/variablesStyles.ts deleted file mode 100644 index 0057ef05..00000000 --- a/src/styles/variablesStyles.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { css } from "@emotion/react"; - -export const variablesStyles = css` - :root { - // colors - --primary-color: #3692ff; - --primary-bg: #cfe5ff; - --white: #fff; - --gray50: #f9fafb; - --gray100: #f3f4f6; - --gray200: #e5e7eb; - --gray300: #d1d5db; - --gray400: #9ca3af; - --gray500: #6b7280; - --gray600: #4b5563; - --gray700: #374151; - --gray800: #1f2937; - --gray900: #111827; - --error: #f74747; - - --footer-bg: #111827; - - // btn bg color - --btn-primary: var(--primary-color); - --btn-primary-hover: #1967d6; - --btn-primary-click: #1251aa; - --btn-disabled: #9ca3af; - - // responsive - --tablet-size: 1199px; - --mobile-size: 767px; - } -`; diff --git a/src/types/authType.ts b/src/types/authType.ts new file mode 100644 index 00000000..d6a708a9 --- /dev/null +++ b/src/types/authType.ts @@ -0,0 +1,4 @@ +export interface ValidResultType { + isValid: boolean | null; + msg: string; +} diff --git a/src/types/productType.ts b/src/types/productType.ts index 91f663dd..bd3d31f3 100644 --- a/src/types/productType.ts +++ b/src/types/productType.ts @@ -1,4 +1,21 @@ export interface ProductItemType { + createdAt: string; + favoriteCount: number; + ownerNickname: string; + ownerId: number; + images: string[]; + tags: string[]; + price: number; + description: string; + name: string; + id: number; +} +export interface ProductListType { + totalCount: number; + list: ProductItemType[]; +} + +export interface ProductItemDetailType { id: number; name: string; description: string; @@ -13,6 +30,11 @@ export interface ProductItemType { isFavorite: boolean; } +export interface InquiryListType { + nextCursor: number; + list: InquiryItemType[]; +} + export interface InquiryItemType { id: number; content: string; diff --git a/src/utils/authUtils.js b/src/utils/authUtils.ts similarity index 69% rename from src/utils/authUtils.js rename to src/utils/authUtils.ts index a691e261..85e8c41d 100644 --- a/src/utils/authUtils.js +++ b/src/utils/authUtils.ts @@ -1,5 +1,7 @@ +import { theme } from "@styles/theme"; + // 닉네임 검사 -export function checkValidNickname(value) { +export function checkValidNickname(value: string) { // 빈값 확인 if (!value.trim().length) return { isValid: false, msg: "닉네임을 입력해주세요." }; @@ -9,7 +11,7 @@ export function checkValidNickname(value) { } // 이메일 검사 -export function checkValidEmail(value) { +export function checkValidEmail(value: string) { // 빈값 확인 if (!value.trim().length) return { isValid: false, msg: "이메일을 입력해주세요." }; @@ -25,7 +27,7 @@ export function checkValidEmail(value) { } // 비밀번호 검사 -export function checkValidPassword(value) { +export function checkValidPassword(value: string) { // 빈값 확인 if (!value.trim().length) return { isValid: false, msg: "비밀번호를 입력해주세요." }; @@ -40,7 +42,7 @@ export function checkValidPassword(value) { } // 비밀번호 확인 검사 -export function checkValidPasswordConfirm(value, password) { +export function checkValidPasswordConfirm(value: string, password: string) { // 빈값 확인 if (!value.trim().length) return { isValid: false, msg: "비밀번호를 입력해주세요." }; @@ -54,8 +56,17 @@ export function checkValidPasswordConfirm(value, password) { } // 인풋 유효성 검사 결과에 따라 클래스명 전달 -export function getAuthValidClassName(isValid) { - if (isValid === null) return ""; +export function getAuthValidStateClassName( + touchedFields: boolean | undefined, + errorMessage: string | undefined +) { + const isBeforeTouch = !touchedFields; + if (isBeforeTouch) return null; + + const isValidError = !!errorMessage; + const borderColor = isValidError + ? theme.colors.error + : theme.colors.primaryColor; - return isValid ? "isPass" : "isError"; + return `border: 1px solid ${borderColor}`; } diff --git a/src/utils/getIsAllValid.js b/src/utils/getIsAllValid.js deleted file mode 100644 index 807e7bc6..00000000 --- a/src/utils/getIsAllValid.js +++ /dev/null @@ -1,3 +0,0 @@ -export function getIsAllValid(valueValids) { - return valueValids.every((valid) => valid.isValid); -} diff --git a/src/utils/getIsAllValid.ts b/src/utils/getIsAllValid.ts new file mode 100644 index 00000000..1927ca49 --- /dev/null +++ b/src/utils/getIsAllValid.ts @@ -0,0 +1,5 @@ +import { ValidResultType } from "types/authType"; + +export function getIsAllValid(valueValids: ValidResultType[]) { + return valueValids.every((valid) => valid.isValid); +} diff --git a/src/utils/getLogo.js b/src/utils/getLogo.ts similarity index 77% rename from src/utils/getLogo.js rename to src/utils/getLogo.ts index c293b011..b276b2a3 100644 --- a/src/utils/getLogo.js +++ b/src/utils/getLogo.ts @@ -3,6 +3,8 @@ import logoMd from "../assets/images/common/logo_md.svg"; import logoSm from "../assets/images/common/logo_sm.svg"; import logoSx from "../assets/images/common/logo_sx.svg"; +type SizeType = keyof typeof logoSize; + const logoSize = { lg: logoLg, md: logoMd, @@ -10,6 +12,6 @@ const logoSize = { sx: logoSx, }; -const getLogo = (size) => logoSize[size]; +const getLogo = (size: SizeType): string => logoSize[size]; export default getLogo;