From db53ca2b2edefbad905d273c19e8ea3c75ad4116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=86=E1=85=B5=E1=86=AB?= =?UTF-8?q?=E1=84=8C=E1=85=A5=E1=86=BC?= Date: Sun, 12 May 2024 05:07:30 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor:=20=EC=A0=84=EB=B0=98=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 파일 경로 수정 - Image, a 태그를 Image, Link태그로 수정 --- components/common/FolderItem.tsx | 20 +++++---- components/common/HeaderElement.tsx | 7 +-- components/common/modals/AddFolderModal.tsx | 4 +- components/common/modals/AddToFolder.tsx | 2 +- components/common/modals/DeleteModal.tsx | 2 +- components/common/modals/EditNameModal.tsx | 2 +- components/common/modals/SharedModal.tsx | 2 +- components/folder/FolderInput.tsx | 2 +- components/folder/FolderTitle.tsx | 6 +-- components/folder/Menus.tsx | 2 +- components/home/CardFrame.tsx | 8 ++-- constants/footerIcons.ts | 8 ++-- constants/socialIcon.ts | 6 +-- next.config.js | 8 ++++ package-lock.json | 47 +++++++++++++++------ package.json | 5 ++- pages/Signup.tsx | 6 +-- 17 files changed, 85 insertions(+), 52 deletions(-) diff --git a/components/common/FolderItem.tsx b/components/common/FolderItem.tsx index c28c1de70..5ec0c9280 100644 --- a/components/common/FolderItem.tsx +++ b/components/common/FolderItem.tsx @@ -1,12 +1,13 @@ import styled from "styled-components"; import { useState } from "react"; import { CalcTime } from "@/utils/calculator"; -import Star from "@/assets/icons/card_star.svg"; -import Kebab from "@/assets/icons/kebab.svg"; -import logo from "@/assets/icons/logo.png"; +import Star from "@/public/assets/icons/card_star.svg"; +import Kebab from "@/public/assets/icons/kebab.svg"; +import logo from "@/public/assets/icons/logo.png"; import { PopOver } from "./modals/PopOver"; import Image from "next/image"; import styles from "@/styles/shared.module.css"; +import Link from "next/link"; import { CommonFolderInfoProps } from "@/constants/commonTypes"; interface FolderItemProps { @@ -29,7 +30,7 @@ function FolderItem({ const img_src = image_source || imageSource; return ( - + {img_src ? ( @@ -64,10 +65,9 @@ function FolderItem({ {description} 2023. 3. 15 - {" "} - * - - + + + ); } @@ -111,12 +111,13 @@ const DefaultImage = styled.div` const TextBox = styled.div` width: 100%; - height: 135px; + height: auto; display: flex; flex-direction: column; gap: 10px; padding: 15px 20px; border-radius: 0px 0px 15px 15px; + text-decoration-line: none; `; const Description = styled.div` @@ -129,6 +130,7 @@ const Description = styled.div` line-height: 24px; margin: 0px; color: #000; + text-decoration: none; display: -webkit-box; -webkit-box-orient: vertical; diff --git a/components/common/HeaderElement.tsx b/components/common/HeaderElement.tsx index df2852d5c..a894e176d 100644 --- a/components/common/HeaderElement.tsx +++ b/components/common/HeaderElement.tsx @@ -1,9 +1,10 @@ import styled from "styled-components"; -import logo from "@/assets/Linkbrary.png"; -import profile from "@/assets/icons/icon_myprofile.png"; +import logo from "@/public/assets/Linkbrary.png"; +import profile from "@/public/assets/icons/icon_myprofile.png"; import { getUserInfo } from "../../api/api"; import { useGetPromise } from "@/hooks/useGetPromise"; import Image from "next/image"; +import Link from "next/link"; interface propTypes { $positionval: string; @@ -16,7 +17,7 @@ function HeaderElement({ $positionval }: propTypes) { return ( - logo + logo {user ? ( diff --git a/components/common/modals/AddFolderModal.tsx b/components/common/modals/AddFolderModal.tsx index 0ede85b0c..dceae1b02 100644 --- a/components/common/modals/AddFolderModal.tsx +++ b/components/common/modals/AddFolderModal.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; import { BlueButton } from "../BlueButton"; -import closeIcon from "@/assets/icons/closeModal.png"; +// import closeIcon from '@/public/assets/icons/closeModal.png'; import Image from "next/image"; import { CommonModalProps } from "@/constants/commonTypes"; @@ -16,7 +16,7 @@ export const AddFolderModal = ({ handleCloseBtn()}> - 닫기 아이콘 + {/* 닫기 아이콘 */} 폴더 추가 diff --git a/components/common/modals/AddToFolder.tsx b/components/common/modals/AddToFolder.tsx index b9d399912..38f1737bc 100644 --- a/components/common/modals/AddToFolder.tsx +++ b/components/common/modals/AddToFolder.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import closeIcon from "@/assets/icons/closeModal.png"; +import closeIcon from "@/public/assets/icons/closeModal.png"; import { BlueButton } from "../BlueButton"; import Image from "next/image"; import { CommonModalProps } from "@/constants/commonTypes"; diff --git a/components/common/modals/DeleteModal.tsx b/components/common/modals/DeleteModal.tsx index f9d7b10b8..a969dc0ab 100644 --- a/components/common/modals/DeleteModal.tsx +++ b/components/common/modals/DeleteModal.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import closeIcon from "@/assets/icons/closeModal.png"; +import closeIcon from "@/public/assets/icons/closeModal.png"; import Image from "next/image"; import { RedButton } from "../../../components/common/RedButton"; import { CommonModalProps } from "@/constants/commonTypes"; diff --git a/components/common/modals/EditNameModal.tsx b/components/common/modals/EditNameModal.tsx index d4f2404d6..d0f9a4d07 100644 --- a/components/common/modals/EditNameModal.tsx +++ b/components/common/modals/EditNameModal.tsx @@ -1,5 +1,5 @@ import { styled } from "styled-components"; -import closeIcon from "@/assets/icons/closeModal.png"; +import closeIcon from "@/public/assets/icons/closeModal.png"; import Image from "next/image"; import { BlueButton } from "../BlueButton"; import { CommonModalProps } from "@/constants/commonTypes"; diff --git a/components/common/modals/SharedModal.tsx b/components/common/modals/SharedModal.tsx index 9fdcc85c7..867dab6fb 100644 --- a/components/common/modals/SharedModal.tsx +++ b/components/common/modals/SharedModal.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import closeIcon from "@/assets/icons/closeModal.png"; +import closeIcon from "@/public/assets/icons/closeModal.png"; import Image from "next/image"; import { CommonModalProps } from "@/constants/commonTypes"; import { SOCIAL_ICONS } from "@/constants/socialIcon"; diff --git a/components/folder/FolderInput.tsx b/components/folder/FolderInput.tsx index f8ed3833d..fd057e3d9 100644 --- a/components/folder/FolderInput.tsx +++ b/components/folder/FolderInput.tsx @@ -1,5 +1,5 @@ import styled from "styled-components"; -import Link from "@/assets/icons/link.svg"; +import Link from "@/public/assets/icons/link.svg"; import { BlueButton } from "../common/BlueButton"; import { forwardRef, SetStateAction, Dispatch } from "react"; diff --git a/components/folder/FolderTitle.tsx b/components/folder/FolderTitle.tsx index c574878ba..403dcfa8b 100644 --- a/components/folder/FolderTitle.tsx +++ b/components/folder/FolderTitle.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; -import Share from "../../assets/icons/share.svg"; -import Pen from "../../assets/icons/pen.svg"; -import Trash from "../../assets/icons/trash.svg"; +import Share from "@/public/assets/icons/share.svg"; +import Pen from "@/public/assets/icons/pen.svg"; +import Trash from "@/public/assets/icons/trash.svg"; type FolderTitlePropsType = { titleName: string; diff --git a/components/folder/Menus.tsx b/components/folder/Menus.tsx index e01a7ce9d..7ff8423bd 100644 --- a/components/folder/Menus.tsx +++ b/components/folder/Menus.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import { getFolderList } from "../../api/api"; import styled from "styled-components"; -import Union from "@/assets/icons/Union.svg"; +import Union from "@/public/assets/icons/Union.svg"; import { COLORS } from "../../constants/colors"; import { useGetPromise } from "@/hooks/useGetPromise"; diff --git a/components/home/CardFrame.tsx b/components/home/CardFrame.tsx index 1736132d1..a9847336a 100644 --- a/components/home/CardFrame.tsx +++ b/components/home/CardFrame.tsx @@ -1,8 +1,8 @@ import styled from "styled-components"; -import card1 from "@/assets/cards/card1.png"; -import card2 from "@/assets/cards/card2.png"; -import card3 from "@/assets/cards/card3.png"; -import card4 from "@/assets/cards/card4.png"; +import card1 from "@/public/assets/cards/card1.png"; +import card2 from "@/public/assets/cards/card2.png"; +import card3 from "@/public/assets/cards/card3.png"; +import card4 from "@/public/assets/cards/card4.png"; import Image, { StaticImageData } from "next/image"; interface propTypes { diff --git a/constants/footerIcons.ts b/constants/footerIcons.ts index 9cf9da328..a831ed989 100644 --- a/constants/footerIcons.ts +++ b/constants/footerIcons.ts @@ -1,7 +1,7 @@ -import facebookIcon from "@/assets/icons/icon_facebook.png"; -import twitterIcon from "@/assets/icons/icon_twitter.png"; -import youtubeIcon from "@/assets/icons/icon_youtube.png"; -import instagramIcon from "@/assets/icons/icon_instagram.png"; +import facebookIcon from "@/public/assets/icons/icon_facebook.png"; +import twitterIcon from "@/public/assets/icons/icon_twitter.png"; +import youtubeIcon from "@/public/assets/icons/icon_youtube.png"; +import instagramIcon from "@/public/assets/icons/icon_instagram.png"; import Image, { StaticImageData } from "next/image"; type footerIconsType = { diff --git a/constants/socialIcon.ts b/constants/socialIcon.ts index 2ec482c63..c0962740d 100644 --- a/constants/socialIcon.ts +++ b/constants/socialIcon.ts @@ -1,6 +1,6 @@ -import kakao from "@/assets/icons/icon_kakao.png"; -import facebook from "@/assets/icons/icon_facebook.png"; -import link from "@/assets/icons/link.png"; +import kakao from "@/public/assets/icons/icon_kakao.png"; +import facebook from "@/public/assets/icons/icon_facebook.png"; +import link from "@/public/assets/icons/link.png"; export const SOCIAL_ICONS = [ { diff --git a/next.config.js b/next.config.js index ccde0b0e2..2fa821e13 100644 --- a/next.config.js +++ b/next.config.js @@ -56,6 +56,14 @@ const nextConfig = { protocol: "https", hostname: "s.pstatic.net", }, + { + protocol: "https", + hostname: "ssl.pstatic.net", + }, + { + protocol: "https", + hostname: "data1.pokemonkorea.co.kr", + }, ], }, }; diff --git a/package-lock.json b/package-lock.json index c5622519c..93f25758e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,9 @@ "dependencies": { "next": "13.5.6", "react": "^18", - "react-dom": "^18", - "styled-components": "^6.1.8" + "react-dom": "^18.3.1", + "styled-components": "^6.1.8", + "swr": "^2.2.5" }, "devDependencies": { "@svgr/webpack": "^8.1.0", @@ -5735,9 +5736,9 @@ ] }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -5746,15 +5747,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-is": { @@ -5987,9 +5988,9 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -6325,6 +6326,18 @@ "url": "https://opencollective.com/svgo" } }, + "node_modules/swr": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -6592,6 +6605,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index a83f30bf7..0837b831b 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,9 @@ "dependencies": { "next": "13.5.6", "react": "^18", - "react-dom": "^18", - "styled-components": "^6.1.8" + "react-dom": "^18.3.1", + "styled-components": "^6.1.8", + "swr": "^2.2.5" }, "devDependencies": { "@svgr/webpack": "^8.1.0", diff --git a/pages/Signup.tsx b/pages/Signup.tsx index 22be9443b..5d53c6eec 100644 --- a/pages/Signup.tsx +++ b/pages/Signup.tsx @@ -2,9 +2,9 @@ import React, { useState, useEffect } from "react"; import styled from "styled-components"; import Image from "next/image"; import Link from "next/link"; -import login_logo from "@/assets/login_logo.png"; -import icon_google from "@/assets/icons/icon_google.png"; -import icon_kakao from "@/assets/icons/icon_kakao.png"; +import login_logo from "@/public/assets/login_logo.png"; +import icon_google from "@/public/assets/icons/icon_google.png"; +import icon_kakao from "@/public/assets/icons/icon_kakao.png"; import { BlueButton } from "@/components/common/BlueButton"; import EmailPwdInput from "@/components/SignInUp/EmailPwdInput"; import { postSignIn } from "@/api/api"; From 8cf15ac6d00965939509ffad5434d6e97ad02a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=86=E1=85=B5=E1=86=AB?= =?UTF-8?q?=E1=84=8C=E1=85=A5=E1=86=BC?= Date: Sun, 12 May 2024 05:09:50 +0900 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9D=84=20=ED=81=B4=EB=A6=B0=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 전에 쓰던 코드 내용이 복잡하고, input으로 props도 많이 넘겨주는 것 같아 코드를 수정했습니다. --- api/api.ts | 5 + components/SignInUp/EmailPwdInput.tsx | 258 +++++++++++--------------- components/common/Input.tsx | 6 +- pages/Signin.tsx | 124 +++++++++---- pages/index.tsx | 2 +- type/inputErrorInfo.ts | 9 + utils/validation.ts | 19 +- 7 files changed, 222 insertions(+), 201 deletions(-) create mode 100644 type/inputErrorInfo.ts diff --git a/api/api.ts b/api/api.ts index 5163a332d..96d68cb01 100644 --- a/api/api.ts +++ b/api/api.ts @@ -53,6 +53,11 @@ export const postSignIn = async (data: any) => { return fetchPOSTJson(url, data); }; +export const postSignUp = async(data: {email: string; password: string}) => { + const url = `${BASE_URL}sign-up`; + return fetchPOSTJson(url, data); +} + export const postCheckDuplicationEmail = async (data: any) => { const url = `${BASE_URL}check-email`; return fetchPOSTJson(url, data); diff --git a/components/SignInUp/EmailPwdInput.tsx b/components/SignInUp/EmailPwdInput.tsx index 3c687de02..b618e4717 100644 --- a/components/SignInUp/EmailPwdInput.tsx +++ b/components/SignInUp/EmailPwdInput.tsx @@ -1,168 +1,137 @@ -import { useState, useEffect, ChangeEvent, KeyboardEvent } from "react"; +import { useState, useRef, useEffect, ChangeEvent, KeyboardEvent } from "react"; import styled from "styled-components"; import Image from "next/image"; -import eyeoff from "@/assets/icons/eye-off.png"; -import eyeon from "@/assets/icons/eye-on.png"; -import { changeInputBorderColor } from "@/utils/commonSigninupFunc"; -import { - emailInputValidationcheck, - loginPasswordInputValidationcheck, - signInPasswordInputValidationcheck, - emailDuplicationCheck, -} from "@/utils/validation"; -import { ERROR_MESSAGE } from "@/constants/errorMessage"; -import INPUT_STATUS from "@/constants/inputStatus"; +import eyeoff from "@/public/assets/icons/eye-off.png"; +import eyeon from "@/public/assets/icons/eye-on.png"; +import INPUT_ERROR_INFO from "@/type/inputErrorInfo"; + type EmailPwdInputPropsType = { title: string; type: string; - valueType: string; - isEyeIcon?: boolean; - setEmailValue?: React.Dispatch>; - emailValue?: string | ""; - setPasswordValue?: React.Dispatch>; - passwordValue?: string | undefined; - onEnterButtonClick: () => void; - loginStatus?: string; - signInStatus?: string; - setIsEmailValid?: React.Dispatch>; - setIsPasswordValid?: React.Dispatch>; - setIsPasswordConfirmValid?: React.Dispatch>; + eventFunc: { + onFocusOut: () => void; + onChange: () => void; + } + inputErrorInfo: INPUT_ERROR_INFO; + inputRef: React.RefObject }; + const EmailPwdInput = ({ title, type, - valueType, - isEyeIcon, - setEmailValue, - emailValue, - setPasswordValue, - passwordValue, - onEnterButtonClick, - loginStatus, - signInStatus, - setIsEmailValid, - setIsPasswordValid, - setIsPasswordConfirmValid, + eventFunc, + inputErrorInfo, + inputRef }: EmailPwdInputPropsType) => { - const INPUT_STATUS_VALUE = { - default: "default", - error: "error", - }; + const [inputStatus, setInputStatus] = useState("default"); const [inputErrorMessage, setInputErrorMessage] = useState(""); const [isViewPassword, setIsViewPassword] = useState(false); - const onInputChangeHandler = (e: ChangeEvent) => { - const inputValue = e.target.value; - if (valueType === "email") { - setEmailValue?.(inputValue); - } else if (valueType === "password") { - setPasswordValue?.(inputValue); - } - }; - - const setInputStatusAndErrorMessage = (status: "valid" | ErrorType) => { - status === "valid" - ? setInputStatus(INPUT_STATUS_VALUE.default) - : (setInputStatus(INPUT_STATUS_VALUE.error), - setInputErrorMessage(status.message)); - }; - - const onInputFocusOutHandler = (e: ChangeEvent) => { - const inputValue = e.target.value; - - switch (valueType) { - case "email": - emailInputFocusOutHandler(inputValue); - break; - case "password": { - passwordInputFocusOutHandler(inputValue); - break; - } - case "password2": - passwordConfirmInputFocusOutHandler(inputValue); - break; - default: - null; - } - }; - - const emailInputFocusOutHandler = async (email: string) => { - const emailInputStatus = emailInputValidationcheck(email); - setInputStatusAndErrorMessage(emailInputStatus); - if (emailInputStatus === "valid") { - setIsEmailValid?.(true); - if (signInStatus) { - const isDuplicateEmail = await emailDuplicationCheck(email); - isDuplicateEmail && - setInputStatusAndErrorMessage(INPUT_STATUS.inUseEmail); - } - } else { - setIsEmailValid?.(false); - } - }; - - const passwordInputFocusOutHandler = async (password: string) => { - let passwordInputStatus; - if (loginStatus) { - passwordInputStatus = loginPasswordInputValidationcheck(password); - } else { - passwordInputStatus = signInPasswordInputValidationcheck(password); - } - setInputStatusAndErrorMessage(passwordInputStatus); - passwordInputStatus === "valid" - ? setIsPasswordValid?.(true) - : setIsPasswordValid?.(false); - }; - - const passwordConfirmInputFocusOutHandler = async (password2: string) => { - const passwordInputStatus = signInPasswordInputValidationcheck( - passwordValue, - password2 - ); - setInputStatusAndErrorMessage(passwordInputStatus); - passwordInputStatus === "valid" - ? setIsPasswordConfirmValid?.(true) - : setIsPasswordConfirmValid?.(false); - }; useEffect(() => { - if (loginStatus === "fail") { - valueType === "email" - ? (setInputStatus(INPUT_STATUS_VALUE.error), - setInputErrorMessage(ERROR_MESSAGE.email.check)) - : (setInputStatus(INPUT_STATUS_VALUE.error), - setInputErrorMessage(ERROR_MESSAGE.password.check)); + if(inputErrorInfo !== "valid") { + setInputErrorMessage(inputErrorInfo.message); + } else { + setInputErrorMessage(''); } - }, [loginStatus]); + + },[inputErrorInfo]) + + // const setInputStatusAndErrorMessage = (status: "valid" | ErrorType) => { + // status === "valid" + // ? setInputStatus(INPUT_STATUS_VALUE.default) + // : (setInputStatus(INPUT_STATUS_VALUE.error), + // setInputErrorMessage(status.message)); + // }; + + // const onInputFocusOutHandler = (e: ChangeEvent) => { + // const inputValue = e.target.value; + + // switch (type) { + // case "email": + // emailInputFocusOutHandler(inputValue); + // break; + // case "password": { + // passwordInputFocusOutHandler(inputValue); + // break; + // } + // case "password2": + // passwordConfirmInputFocusOutHandler(inputValue); + // break; + // default: + // null; + // } + // }; + + // const emailInputFocusOutHandler = async (email: string) => { + // const emailInputStatus = emailInputValidationcheck(email); + // setInputStatusAndErrorMessage(emailInputStatus); + // if (emailInputStatus === "valid") { + // setIsEmailValid?.(true); + // if (signInStatus) { + // const isDuplicateEmail = await emailDuplicationCheck(email); + // isDuplicateEmail && + // setInputStatusAndErrorMessage(INPUT_STATUS.inUseEmail); + // } + // } else { + // setIsEmailValid?.(false); + // } + // }; + + // const passwordInputFocusOutHandler = async (password: string) => { + // let passwordInputStatus; + // if (loginStatus) { + // passwordInputStatus = loginPasswordInputValidationcheck(password); + // } else { + // passwordInputStatus = signInPasswordInputValidationcheck(password); + // } + // setInputStatusAndErrorMessage(passwordInputStatus); + // passwordInputStatus === "valid" + // ? setIsPasswordValid?.(true) + // : setIsPasswordValid?.(false); + // }; + + // const passwordConfirmInputFocusOutHandler = async (password2: string) => { + // const passwordInputStatus = signInPasswordInputValidationcheck( + // passwordValue, + // password2 + // ); + // setInputStatusAndErrorMessage(passwordInputStatus); + // passwordInputStatus === "valid" + // ? setIsPasswordConfirmValid?.(true) + // : setIsPasswordConfirmValid?.(false); + // }; + + // useEffect(() => { + // if (loginStatus === "fail") { + // valueType === "email" + // ? (setInputStatus(INPUT_STATUS_VALUE.error), + // setInputErrorMessage(ERROR_MESSAGE.email.check)) + // : (setInputStatus(INPUT_STATUS_VALUE.error), + // setInputErrorMessage(ERROR_MESSAGE.password.check)); + // } + // }, [loginStatus]); + + // const onInputFocusHandler = () => { + // setInputStatus("writing"); + // }; - const onInputFocusHandler = () => { - setInputStatus("writing"); - }; - - const KeyEventHandler = (e: KeyboardEvent) => { - const inputElement = e.target as HTMLInputElement; - if (e.key === "Enter") { - onEnterButtonClick(); - inputElement.blur(); - } - }; return (

{title}

- {isEyeIcon && ( + {type==="password" && ( { setIsViewPassword(!isViewPassword); @@ -171,31 +140,24 @@ const EmailPwdInput = ({ eye_off_icon )} - {inputStatus === "error" && ( + {inputErrorMessage && ( {inputErrorMessage} )}
); }; -type ErrorType = - | { - errorName: string; - type: string; - message: string; - } - | "valid"; const InputDiv = styled.div` width: 100%; margin-bottom: 24px; position: relative; `; -const EmailPasswordInput = styled.input<{ status: string }>` +const EmailPasswordInput = styled.input<{ inputErrorInfo: INPUT_ERROR_INFO }>` width: 100%; padding: 18px 15px; - border: ${({ status }) => - `1px solid ${changeInputBorderColor(status) ?? "var(--Grey_300)"}`}; + border: ${({ inputErrorInfo }) => + `1px solid ${inputErrorInfo === "valid" ? "var(--Grey_300)" : "red"}`}; border-radius: 8px; outline: none; margin-top: 12px; diff --git a/components/common/Input.tsx b/components/common/Input.tsx index 64f30f5bc..3ed0928ea 100644 --- a/components/common/Input.tsx +++ b/components/common/Input.tsx @@ -1,7 +1,7 @@ -import react, { KeyboardEvent } from "react"; +import { KeyboardEvent } from "react"; import styled from "styled-components"; -import searchIcon from "../../assets/icons/icon_search.png"; -import Delete from "@/assets/icons/delete.svg"; +import searchIcon from "@/public/assets/icons/icon_search.png"; +import Delete from "@/public/assets/icons/delete.svg"; import Image from "next/image"; type InputPropsType = { diff --git a/pages/Signin.tsx b/pages/Signin.tsx index 87d3da8fe..94ec4c22b 100644 --- a/pages/Signin.tsx +++ b/pages/Signin.tsx @@ -1,41 +1,95 @@ -import React, { useState } from "react"; +"use client"; + +import React, { useState, useRef } from "react"; import styled from "styled-components"; import Image from "next/image"; import Link from "next/link"; -import login_logo from "@/assets/login_logo.png"; -import icon_google from "@/assets/icons/icon_google.png"; -import icon_kakao from "@/assets/icons/icon_kakao.png"; +import login_logo from "@/public/assets/login_logo.png"; +import icon_google from "@/public/assets/icons/icon_google.png"; +import icon_kakao from "@/public/assets/icons/icon_kakao.png"; import { BlueButton } from "@/components/common/BlueButton"; import EmailPwdInput from "@/components/SignInUp/EmailPwdInput"; +import { postSignIn } from "@/api/api"; +import INPUT_ERROR_INFO from "@/type/inputErrorInfo"; +import { emailInputValidationcheck, loginPasswordInputValidationcheck } from "@/utils/validation"; +import { useToast } from "@/contexts/ToastContext"; -export default function Signup() { +export default function Signin() { const [emailValue, setEmailValue] = useState(""); - const [passwordValue, setPasswordValue] = useState(""); - const [isEmailValid, setIsEmailValid] = useState(false); - const [isPasswordValid, setIsPasswordValid] = useState(false); - const [isPasswordConfirmValid, setIsPasswordConfirmValid] = useState(false); - const [signInStatus, setSignInStatus] = useState("normal"); - - const trySignin = () => { - if (isEmailValid && isPasswordValid && isPasswordConfirmValid) { - console.log("회원가입 시도"); - location.assign("/Folder"); - } else { - setSignInStatus("fail"); - } + const [emailErrorInfo, setEmailErrorInfo] = useState("valid"); + const emailInputRef = useRef(null); + + const [passwordValue, setPasswordValue] = useState(""); + const [passwordErrorInfo, setPasswordErrorInfo] = useState("valid"); + const passwordInputRef = useRef(null); + + const toast = useToast(); + + + const trySignin = async () => { + let result; + if (emailErrorInfo==="valid" && passwordErrorInfo === "valid") { + const postJsonValue = {email: emailValue, password: passwordValue}; + try{ + result = await postSignIn(postJsonValue); + } catch (error){ + console.error("로그인 오류"); + } + + if(result.data) { + location.assign('/folder'); + } else if (result.error) { + toast.setViewToast(true); + toast.setText("로그인에 성공하지 못햇습니다."); + console.log(result); + } }; +} + + const emailFocusOutHandler = () => { + const emailInputStatus = emailInputValidationcheck(emailValue); + setEmailErrorInfo(emailInputStatus); + } + + const emailChangeHandler = () => { + if(emailInputRef.current) { + setEmailValue(emailInputRef.current.value) + } + } + + const passwordFocusOutHandler = () => { + const passwordStatus = loginPasswordInputValidationcheck(passwordValue) + setPasswordErrorInfo(passwordStatus); + } + + const passwordChangeHandler = () => { + if(passwordInputRef.current) { + setPasswordValue(passwordInputRef.current.value) + } + } + + const emailventFunc = { + onFocusOut: emailFocusOutHandler, + onChange: emailChangeHandler + } + + const passwordEventFunc = { + onFocusOut: passwordFocusOutHandler, + onChange: passwordChangeHandler, + } + return ( - login log + logo

- 이미 회원이신가요?{" "} + 회원이 아니신가요?{" "} - 로그인 하기 + 회원가입 하기

@@ -43,28 +97,18 @@ export default function Signup() { + eventFunc = {emailventFunc} + inputErrorInfo = {emailErrorInfo} + inputRef = {emailInputRef} + /> + { /* + /> */} { - let status: ErrorType; + let emailValid: EmailValid; if (!email) { - status = INPUT_STATUS.noEmail; + emailValid = INPUT_STATUS.noEmail; } else if (!emailCheck(email)) { - status = INPUT_STATUS.wrongEmail; + emailValid = INPUT_STATUS.wrongEmail; } else { - status = "valid"; + emailValid = "valid"; } - return status; + return emailValid; }; // 이메일 중복 검사 @@ -40,8 +41,8 @@ export const emailDuplicationCheck = async (email: string) => { // 로그인할 때 비밀번호 에러 검사 export const loginPasswordInputValidationcheck = ( password: string | undefined -): ErrorType => { - let status: ErrorType; +): EmailValid => { + let status: EmailValid; if (!password) { status = INPUT_STATUS.noPassword; } else { From 06623affdf96dbe9ac54f41dee40e221c426e1e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=86=E1=85=B5=E1=86=AB?= =?UTF-8?q?=E1=84=8C=E1=85=A5=E1=86=BC?= Date: Sun, 12 May 2024 05:12:25 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - modal portal을 이용해 토스트 컴포넌트 화면에 띄우도록 하였고, context API를 이용해 전역적으로 관리가 가능하도록 구현하였습니다. --- components/SignInUp/Input.tsx | 0 components/common/Portal.tsx | 19 +++++++ components/common/Toast.tsx | 69 +++++++++++++++++++++++ components/common/ToastPortalContent.tsx | 7 +++ constants/errorText.ts | 0 contexts/ToastContext.tsx | 44 +++++++++++++++ pages/ToastTest.tsx | 21 +++++++ pages/_app.tsx | 13 ++++- pages/_document.tsx | 2 +- public/assets/icons/close.png | Bin 0 -> 850 bytes 10 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 components/SignInUp/Input.tsx create mode 100644 components/common/Portal.tsx create mode 100644 components/common/Toast.tsx create mode 100644 components/common/ToastPortalContent.tsx create mode 100644 constants/errorText.ts create mode 100644 contexts/ToastContext.tsx create mode 100644 pages/ToastTest.tsx create mode 100644 public/assets/icons/close.png diff --git a/components/SignInUp/Input.tsx b/components/SignInUp/Input.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/components/common/Portal.tsx b/components/common/Portal.tsx new file mode 100644 index 000000000..3a0b870b4 --- /dev/null +++ b/components/common/Portal.tsx @@ -0,0 +1,19 @@ +import React, { useEffect } from 'react'; +import { createPortal } from 'react-dom'; + +type PortalProps = { + children: React.ReactNode; +} + +const Portal = ({ + children +}: PortalProps) => { + + const modalRoot = typeof document !== 'undefined' ? document.getElementById('modal-root') : null; + + if(!modalRoot) return; + + return createPortal(children, modalRoot) +} + +export default Portal \ No newline at end of file diff --git a/components/common/Toast.tsx b/components/common/Toast.tsx new file mode 100644 index 000000000..fa144229a --- /dev/null +++ b/components/common/Toast.tsx @@ -0,0 +1,69 @@ +import React, { useEffect } from 'react'; +import styled from 'styled-components'; +import closeIcon from '@/public/assets/icons/close.png'; +import Image from 'next/image'; + +type ToastType = { + errorMessage?:string | null; + isToastView: boolean, + setIsToastView: React.Dispatch>; +} +const Toast = ({errorMessage, isToastView, setIsToastView}:ToastType) => { + + const onClickCloseIconHandler = () => { + setIsToastView(false); + } + + useEffect(() => { + setTimeout(() => { + setIsToastView(false); + }, 3000); + },[isToastView]); + + return ( + + + {errorMessage} + + + closeIcon + + + ) +} + +export default Toast; + +const ToastContainer = styled.div<{ isToastView: boolean } >` + width: 50%; + padding: 10px 20px; + background-color: #dfcbff; + border: 1px solid var(--Primary); + border-radius: 10px;; + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 9999; + text-align: center; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + box-shadow: 0px 10px 29px -14px rgba(0,0,0,1); + visibility: ${({isToastView}) => isToastView ? "visible" : "hidden"}; +`; + +const ErrorMessageDiv = styled.div` + display: flex; + align-items: center; + color: #4600b8; + padding-top: 3px; +`; + +const CloseIconDiv = styled.div` + display: flex; + align-items: center; + cursor: pointer; + +`; diff --git a/components/common/ToastPortalContent.tsx b/components/common/ToastPortalContent.tsx new file mode 100644 index 000000000..6c7c7d9dc --- /dev/null +++ b/components/common/ToastPortalContent.tsx @@ -0,0 +1,7 @@ +const ToastPortalContent = ({children}: {children?:any}) => { + return ( + children + ) +} + +export default ToastPortalContent \ No newline at end of file diff --git a/constants/errorText.ts b/constants/errorText.ts new file mode 100644 index 000000000..e69de29bb diff --git a/contexts/ToastContext.tsx b/contexts/ToastContext.tsx new file mode 100644 index 000000000..4431437f8 --- /dev/null +++ b/contexts/ToastContext.tsx @@ -0,0 +1,44 @@ +import { createContext, useContext, useState } from 'react'; +import Toast from '@/components/common/Toast'; +import ToastPortalContent from '@/components/common/ToastPortalContent'; + +type ToastContextType = { + setText: (message: string) => void; + setViewToast: (value: boolean) => void; +} + +const ToastContext = createContext(undefined); + +export const useToast = () =>{ + const context = useContext(ToastContext); + if(!context) throw new Error("toast error"); + return context; +} + +export const ToastProvider = ({children}: {children: any}) => { + + const [toastText, setToastText] = useState(''); + const [isToastView, setIsToastView] = useState(false); + + const setText = (message: string) => { + setToastText(message); + } + + const setViewToast = (value : boolean) => { + setIsToastView(value); + } + + const contextValue: ToastContextType = { + setText, + setViewToast + } + + return ( + + {children} + + + + + ) +} \ No newline at end of file diff --git a/pages/ToastTest.tsx b/pages/ToastTest.tsx new file mode 100644 index 000000000..68048eed8 --- /dev/null +++ b/pages/ToastTest.tsx @@ -0,0 +1,21 @@ +import React, { useEffect, useState } from 'react'; +import { useToast } from '@/contexts/ToastContext'; + +const ToastTest = () => { + + const { setText, setViewToast } = useToast(); + + useEffect(() => { + setText('ddddd'); + }) + + + return ( +
+ + +
+ ) +} + +export default ToastTest \ No newline at end of file diff --git a/pages/_app.tsx b/pages/_app.tsx index f0d4879e4..935b31fe2 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,11 +1,18 @@ import "@/styles/globals.css"; import type { AppProps } from "next/app"; import "@/styles/globals.css"; +import { ToastProvider } from "@/contexts/ToastContext"; +import Portal from "@/components/common/Portal"; +import ToastPortalContent from "@/components/common/ToastPortalContent"; + export default function App({ Component, pageProps }: AppProps) { return ( - <> - - + + + + + + ); } diff --git a/pages/_document.tsx b/pages/_document.tsx index ae39f9b65..f5368e077 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -5,7 +5,7 @@ export default function MyDocument(props: any) { return ( {/* Place your head elements here */} - +
diff --git a/public/assets/icons/close.png b/public/assets/icons/close.png new file mode 100644 index 0000000000000000000000000000000000000000..de8e32570da1e62cc9617facd4ef41d957aaeff3 GIT binary patch literal 850 zcmV-Y1FigtP)`PG&YU)x}o_75QHiEQkXo z6PfJ^xf<)NZ+j8da=r z78TdTdvhXYM0rIV9u*xMVMj&BIdNo6l&3|U662d1Ul0ds#$VVJ{2v9Uvqzz!OZrOxl4T9(tjy_{7n4(spxI# zCPeR;m^m$eXzA{7J~N-#ahLsV1ew_g4SjYa&?aG6v+B|3#&PGx%vV_%jYN$c6(%fGQ@tOxEzgO(r>W3yo z?>)Y+3Ti$vpLym!mtLg*+{o~d=-e$vmbjSd&WvAIJP@9?R2vl?nX!_$tE`&OOeQWba|`}zw(EUWyM8tk zneheQ^-#Os@WUAqgPDoMjtxGV)_kMEiHC3gNCFrZTkf(yHydGpW@}~ix5R8>s)wn1 c%>RG#4@S;&00ex6=>Px#07*qoM6N<$f)!qjuK)l5 literal 0 HcmV?d00001 From 050040542ef23c2ff4f3ca80ccc8144e7d8ff6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=86=E1=85=B5=E1=86=AB?= =?UTF-8?q?=E1=84=8C=E1=85=A5=E1=86=BC?= Date: Sun, 12 May 2024 14:28:02 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 토스트 반응형으로 수정 - 인풋에 키보드 이벤트 추가 --- components/SignInUp/EmailPwdInput.tsx | 2 ++ components/common/Toast.tsx | 5 +++++ pages/Signin.tsx | 18 ++++++++++++++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/components/SignInUp/EmailPwdInput.tsx b/components/SignInUp/EmailPwdInput.tsx index b618e4717..bc029d193 100644 --- a/components/SignInUp/EmailPwdInput.tsx +++ b/components/SignInUp/EmailPwdInput.tsx @@ -12,6 +12,7 @@ type EmailPwdInputPropsType = { eventFunc: { onFocusOut: () => void; onChange: () => void; + onKeydown: (e: React.KeyboardEvent) => void; } inputErrorInfo: INPUT_ERROR_INFO; inputRef: React.RefObject @@ -129,6 +130,7 @@ const EmailPwdInput = ({ inputErrorInfo={inputErrorInfo} onChange={eventFunc.onChange} onBlur={eventFunc.onFocusOut} + onKeyDown={eventFunc.onKeydown} ref={inputRef} /> {type==="password" && ( diff --git a/components/common/Toast.tsx b/components/common/Toast.tsx index fa144229a..a7101cc88 100644 --- a/components/common/Toast.tsx +++ b/components/common/Toast.tsx @@ -52,6 +52,11 @@ const ToastContainer = styled.div<{ isToastView: boolean } >` align-items: center; box-shadow: 0px 10px 29px -14px rgba(0,0,0,1); visibility: ${({isToastView}) => isToastView ? "visible" : "hidden"}; + + @media (max-width: 767px) { + width: 90%; + font-size: 13px; + } `; const ErrorMessageDiv = styled.div` diff --git a/pages/Signin.tsx b/pages/Signin.tsx index 94ec4c22b..c89485c3b 100644 --- a/pages/Signin.tsx +++ b/pages/Signin.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useRef } from "react"; +import React, { useState, useRef, ChangeEvent } from "react"; import styled from "styled-components"; import Image from "next/image"; import Link from "next/link"; @@ -43,8 +43,8 @@ export default function Signin() { toast.setText("로그인에 성공하지 못햇습니다."); console.log(result); } - }; -} + }; + } const emailFocusOutHandler = () => { const emailInputStatus = emailInputValidationcheck(emailValue); @@ -67,15 +67,25 @@ export default function Signin() { setPasswordValue(passwordInputRef.current.value) } } + + const onEnterKeyHandler = (e:React.KeyboardEvent) => { + if(e.key === 'Enter') { + emailInputRef?.current?.blur(); + passwordInputRef?.current?.blur(); + trySignin(); + } + } const emailventFunc = { onFocusOut: emailFocusOutHandler, - onChange: emailChangeHandler + onChange: emailChangeHandler, + onKeydown: onEnterKeyHandler, } const passwordEventFunc = { onFocusOut: passwordFocusOutHandler, onChange: passwordChangeHandler, + onKeydown: onEnterKeyHandler, } From 946df4e33fc70c632868a00a9448b65ea4db276e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=86=E1=85=B5=E1=86=AB?= =?UTF-8?q?=E1=84=8C=E1=85=A5=E1=86=BC?= Date: Sun, 12 May 2024 15:19:15 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/Signin.tsx | 26 +++----- pages/Signup.tsx | 157 ++++++++++++++++++++++++++++++-------------- utils/validation.ts | 21 +++--- 3 files changed, 126 insertions(+), 78 deletions(-) diff --git a/pages/Signin.tsx b/pages/Signin.tsx index c89485c3b..2a6be9537 100644 --- a/pages/Signin.tsx +++ b/pages/Signin.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useRef, ChangeEvent } from "react"; +import React, { useState, useRef, useEffect } from "react"; import styled from "styled-components"; import Image from "next/image"; import Link from "next/link"; @@ -13,6 +13,7 @@ import { postSignIn } from "@/api/api"; import INPUT_ERROR_INFO from "@/type/inputErrorInfo"; import { emailInputValidationcheck, loginPasswordInputValidationcheck } from "@/utils/validation"; import { useToast } from "@/contexts/ToastContext"; +import { localStorage } from "@/utils/localStorage"; export default function Signin() { const [emailValue, setEmailValue] = useState(""); @@ -26,6 +27,12 @@ export default function Signin() { const toast = useToast(); + useEffect(() => { + localStorage.remove("accssToken"); + // localStorage.get("accessToken") && location.assign("/Folder"); + }, []); + + const trySignin = async () => { let result; if (emailErrorInfo==="valid" && passwordErrorInfo === "valid") { @@ -37,7 +44,8 @@ export default function Signin() { } if(result.data) { - location.assign('/folder'); + localStorage.save("accessToken", result.data.accessToken); + location.assign("/Folder"); } else if (result.error) { toast.setViewToast(true); toast.setText("로그인에 성공하지 못햇습니다."); @@ -118,20 +126,6 @@ export default function Signin() { inputErrorInfo={passwordErrorInfo} inputRef={passwordInputRef} /> - { /* - */} (""); - const [loginStatus, setLoginStatus] = useState("normal"); - - useEffect(() => { - localStorage.remove("accssToken"); - // localStorage.get("accessToken") && location.assign("/Folder"); - }, []); - - const tryLogin = async () => { - const postJsonValue = { email: emailValue, password: passwordValue }; - let result; - try { - result = await postSignIn(postJsonValue); - } catch (error) { - console.error(error); - } + const [emailErrorInfo, setEmailErrorInfo] = useState("valid"); + const emailInputRef = useRef(null); - if (result.data) successLogin(result); - else failLogin(); - }; + const [passwordValue, setPasswordValue] = useState(""); + const [passwordErrorInfo, setPasswordErrorInfo] = useState("valid"); + const passwordInputRef = useRef(null); - const successLogin = (result: any) => { - setLoginStatus("success"); - localStorage.save("accessToken", result.data.accessToken); - location.assign("/Folder"); - }; + const [passwordCheckValue, setPasswordCheckValue] = useState(""); + const [passwordCheckErrorInfo, setPasswordCheckErrorInfo] = useState("valid"); + const passwordCheckInputRef = useRef(null); + + const toast = useToast(); + + const trySignup = async () => { + if(emailErrorInfo === "valid" && passwordErrorInfo === "valid" && passwordCheckErrorInfo === "valid") { + const postJsonValue = { email: emailValue, password: passwordValue }; + let result; + try { + result = await postSignUp(postJsonValue); + } catch (error) { + console.error(error); + } - const failLogin = () => { - setLoginStatus("fail"); + if(result.error) { + toast.setText("회원가입에 실패하였습니다."); + toast.setViewToast(true); + } + } }; + const onEnterKeyHandler = (e: React.KeyboardEvent) => { + if(e.key === "Enter") { + emailInputRef.current?.blur(); + passwordInputRef.current?.blur(); + passwordCheckInputRef.current?.blur(); + trySignup(); + } + } + + const emailChangeHandler = () => { + if(emailInputRef.current) { + setEmailValue(emailInputRef.current.value); + } + } + const emailFocusoutHandler = () => { + const emailErrorInfo = emailInputValidationcheck(emailValue); + setEmailErrorInfo(emailErrorInfo); + } + + const passwordChangeHandler = () => { + if(passwordInputRef.current) { + setPasswordValue(passwordInputRef.current.value); + } + } + + const passwordFocusoutHandler = () => { + const passwordErrorInfo = signUpPasswordInputValidationcheck(passwordValue); + setPasswordErrorInfo(passwordErrorInfo); + } + + const passwordCheckChangeHandler = () => { + if(passwordCheckInputRef.current) { + setPasswordCheckValue(passwordCheckInputRef.current.value); + } + } + + const passwordCheckFocusoutHandler = () => { + const passwordCheckErrorInfo = signUpPasswordInputValidationcheck(passwordValue, passwordCheckValue); + setPasswordCheckErrorInfo(passwordCheckErrorInfo); + } + + const emailEventFunc = { + onChange: emailChangeHandler, + onFocusOut: emailFocusoutHandler, + onKeydown: onEnterKeyHandler, + } + + const passwordEventFunc = { + onChange: passwordChangeHandler, + onFocusOut: passwordFocusoutHandler, + onKeydown: onEnterKeyHandler, + } + + const passwordCheckEventFunc = { + onChange: passwordCheckChangeHandler, + onFocusOut: passwordCheckFocusoutHandler, + onKeydown: onEnterKeyHandler, + } + return ( @@ -51,9 +111,9 @@ export default function LogIn() { login log

- 회원이 아니신가요?{" "} + 이미 회원이신가요?{" "} - 회원 가입하기 + 로그인 하기

@@ -61,35 +121,34 @@ export default function LogIn() { + 소셜 로그인 diff --git a/utils/validation.ts b/utils/validation.ts index a3c72a0d0..3b82295d5 100644 --- a/utils/validation.ts +++ b/utils/validation.ts @@ -7,7 +7,7 @@ function emailCheck(email_address: string) { return Boolean(email_regex.test(email_address)); } -type EmailValid = +type ErrorValid = // error { errorName: string; @@ -17,8 +17,8 @@ type EmailValid = | "valid"; // 이메일 에러 검사 -export const emailInputValidationcheck = (email: string): ErrorType => { - let emailValid: EmailValid; +export const emailInputValidationcheck = (email: string): ErrorValid => { + let emailValid: ErrorValid; if (!email) { emailValid = INPUT_STATUS.noEmail; } else if (!emailCheck(email)) { @@ -41,8 +41,8 @@ export const emailDuplicationCheck = async (email: string) => { // 로그인할 때 비밀번호 에러 검사 export const loginPasswordInputValidationcheck = ( password: string | undefined -): EmailValid => { - let status: EmailValid; +): ErrorValid => { + let status: ErrorValid; if (!password) { status = INPUT_STATUS.noPassword; } else { @@ -52,11 +52,11 @@ export const loginPasswordInputValidationcheck = ( }; // 회원가입할 때 비밀번호 에러 검사 -export const signInPasswordInputValidationcheck = ( +export const signUpPasswordInputValidationcheck = ( password: string | undefined, password2?: string | undefined -): ErrorType => { - let status: ErrorType; +): ErrorValid => { + let status: ErrorValid; if (!password) { status = INPUT_STATUS.noPassword; } else if (!passwordCheck(password)) { @@ -76,9 +76,4 @@ function passwordCheck(password: string): boolean { return password_regex.test(password); } -// 비밀번호 중복 확인 -export function isMatch(password1: string, password2: string) { - return password1 === password2; -} - export { emailCheck, passwordCheck }; From 5358d3381462cb18f0ffb2da8b2e276031578943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=86=E1=85=B5=E1=86=AB?= =?UTF-8?q?=E1=84=8C=E1=85=A5=E1=86=BC?= Date: Mon, 13 May 2024 01:22:59 +0900 Subject: [PATCH 6/7] =?UTF-8?q?Refactor:=20=EB=AA=A8=EB=8B=AC=EC=B0=BD=20?= =?UTF-8?q?=EC=A0=84=EC=97=AD=20=EC=83=81=ED=83=9C=EB=A1=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/common/FolderItem.tsx | 35 ++++++--- components/common/FolderList.tsx | 20 +---- components/common/Portal.tsx | 2 +- components/common/modals/AddFolderModal.tsx | 35 +++++---- components/common/modals/AddToFolder.tsx | 40 +++++----- components/common/modals/DeleteModal.tsx | 75 ++++++++++--------- components/common/modals/EditNameModal.tsx | 31 ++++---- .../common/modals/ModalPortalContent.tsx | 0 components/common/modals/PopOver.tsx | 52 +++++++------ components/common/modals/SharedModal.tsx | 30 ++++---- components/folder/FolderInput.tsx | 3 +- components/folder/FolderTitle.tsx | 22 ++++-- components/folder/Menus.tsx | 12 ++- components/shared/SharedSection.tsx | 2 +- contexts/ModalContext.tsx | 44 +++++++++++ hooks/useOutSideClick.ts | 22 ++++++ pages/Folder.tsx | 22 +----- pages/ModalTest.tsx | 21 ++++++ pages/Shared.tsx | 13 ---- pages/Signin.tsx | 2 +- pages/Signup.tsx | 6 +- pages/_app.tsx | 12 +-- 22 files changed, 301 insertions(+), 200 deletions(-) create mode 100644 components/common/modals/ModalPortalContent.tsx create mode 100644 contexts/ModalContext.tsx create mode 100644 hooks/useOutSideClick.ts create mode 100644 pages/ModalTest.tsx diff --git a/components/common/FolderItem.tsx b/components/common/FolderItem.tsx index 5ec0c9280..03f87f30c 100644 --- a/components/common/FolderItem.tsx +++ b/components/common/FolderItem.tsx @@ -9,17 +9,16 @@ import Image from "next/image"; import styles from "@/styles/shared.module.css"; import Link from "next/link"; import { CommonFolderInfoProps } from "@/constants/commonTypes"; +import { useModal } from "@/contexts/ModalContext"; +import { DeleteModal } from "./modals/DeleteModal"; +import { AddToFolder } from "./modals/AddToFolder"; interface FolderItemProps { item: CommonFolderInfoProps; - $isModalVisible: string; - setIsModalVisible: any; } function FolderItem({ item, - $isModalVisible, - setIsModalVisible, }: FolderItemProps) { const { imageSource, createdAt, description, url, id } = item; const { created_at, favorite, image_source } = item; @@ -29,6 +28,27 @@ function FolderItem({ const time = CalcTime(createdAtTime); const img_src = image_source || imageSource; + const modal = useModal(); + + const openDeleteModal = () => { + modal.openModal(); + }; + + const openAddToFolderModal = () => { + modal.openModal() + }; + + const POPOVER_INFO = [ + { + option: "삭제하기", + callback: openDeleteModal + }, + { + option: "폴더에 추가", + callback: openAddToFolderModal + } + ] + return ( @@ -50,16 +70,13 @@ function FolderItem({ e.preventDefault(); setIsPopOverVisible(!isPopOverVisible); }} - /> + /> {description} diff --git a/components/common/FolderList.tsx b/components/common/FolderList.tsx index b9691f6ac..860827e8d 100644 --- a/components/common/FolderList.tsx +++ b/components/common/FolderList.tsx @@ -4,30 +4,18 @@ import { CommonFolderInfoProps } from "@/constants/commonTypes"; type FolderPropsType = { items: CommonFolderInfoProps[]; - $isModalVisible: any; - setIsModalVisible: any; }; function FolderList({ items, - $isModalVisible, - setIsModalVisible, }: FolderPropsType) { + return ( - {items.map((item: CommonFolderInfoProps) => { - { - return ( - - ); - } - })} + {items.map((item: CommonFolderInfoProps) => ( + + ))} ); diff --git a/components/common/Portal.tsx b/components/common/Portal.tsx index 3a0b870b4..7daef19bd 100644 --- a/components/common/Portal.tsx +++ b/components/common/Portal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { createPortal } from 'react-dom'; type PortalProps = { diff --git a/components/common/modals/AddFolderModal.tsx b/components/common/modals/AddFolderModal.tsx index dceae1b02..13495f4c0 100644 --- a/components/common/modals/AddFolderModal.tsx +++ b/components/common/modals/AddFolderModal.tsx @@ -1,22 +1,27 @@ +import { useRef } from "react"; import styled from "styled-components"; import { BlueButton } from "../BlueButton"; -// import closeIcon from '@/public/assets/icons/closeModal.png'; +import closeIcon from '@/public/assets/icons/closeModal.png'; import Image from "next/image"; -import { CommonModalProps } from "@/constants/commonTypes"; +import { useModal } from "@/contexts/ModalContext"; +import useOutSideClick from "@/hooks/useOutSideClick"; -export const AddFolderModal = ({ - isModalVisible, - setIsModalVisible, -}: CommonModalProps) => { - const handleCloseBtn = () => { - setIsModalVisible(""); - }; +export const AddFolderModal = () => { + const modal = useModal(); + const modalRef = useRef(null); + + const onCloseModal = () => { + modal.closeModal(); + } + + useOutSideClick({ ref: modalRef, callback: onCloseModal}); + return ( - - - handleCloseBtn()}> - {/* 닫기 아이콘 */} + + + + 닫기 아이콘 폴더 추가 @@ -34,9 +39,7 @@ export const AddFolderModal = ({ ); }; -const Background = styled.div<{ $isVisible: string }>` - display: ${({ $isVisible }) => - $isVisible === "폴더 추가" ? "block" : "none"}; +const Background = styled.div` z-index: 9999; position: fixed; top: 0; diff --git a/components/common/modals/AddToFolder.tsx b/components/common/modals/AddToFolder.tsx index 38f1737bc..303515c08 100644 --- a/components/common/modals/AddToFolder.tsx +++ b/components/common/modals/AddToFolder.tsx @@ -1,30 +1,26 @@ +import { useRef } from "react"; import styled from "styled-components"; import closeIcon from "@/public/assets/icons/closeModal.png"; import { BlueButton } from "../BlueButton"; import Image from "next/image"; -import { CommonModalProps } from "@/constants/commonTypes"; +import useOutSideClick from "@/hooks/useOutSideClick"; +import { useModal } from "@/contexts/ModalContext"; -export const AddToFolder = ({ - isModalVisible, - setIsModalVisible, -}: CommonModalProps) => { - const handleCloseBtn = () => { - setIsModalVisible(""); - }; +export const AddToFolder = () => { + + const modal = useModal(); + const modalRef = useRef(null); + + const onCloseModal = () => { + modal.closeModal(); + } + + useOutSideClick({ ref: modalRef, callback: onCloseModal}); return ( - { - e.preventDefault(); - }} - > - - { - handleCloseBtn(); - }} - > + + + closeIcon @@ -59,9 +55,7 @@ export const AddToFolder = ({ ); }; -const Background = styled.div<{ $isVisible: string }>` - display: ${({ $isVisible }) => - $isVisible === "폴더에 추가" ? "block" : "none"}; +const Background = styled.div` z-index: 9999; position: fixed; top: 0; diff --git a/components/common/modals/DeleteModal.tsx b/components/common/modals/DeleteModal.tsx index a969dc0ab..1d6593dcd 100644 --- a/components/common/modals/DeleteModal.tsx +++ b/components/common/modals/DeleteModal.tsx @@ -1,48 +1,51 @@ +import { useRef } from "react"; import styled from "styled-components"; import closeIcon from "@/public/assets/icons/closeModal.png"; import Image from "next/image"; -import { RedButton } from "../../../components/common/RedButton"; -import { CommonModalProps } from "@/constants/commonTypes"; +import { RedButton } from "../RedButton"; +import { useModal } from "@/contexts/ModalContext"; +import useOutSideClick from "@/hooks/useOutSideClick"; -export const DeleteModal = ({ - isModalVisible, - setIsModalVisible, -}: CommonModalProps) => { - const handleCloseBtn = () => { - setIsModalVisible(""); - }; + +export const DeleteModal = () => { + + const modal = useModal(); + const modalRef = useRef<HTMLDivElement>(null); + + const onCloseModal = () => { + modal.closeModal(); + } + + useOutSideClick({ ref: modalRef, callback: onCloseModal}); return ( - <Background $isVisible={isModalVisible}> - <Modal> - <Close - onClick={(e) => { - e.preventDefault(); - handleCloseBtn(); - }} - > - <Image src={closeIcon} alt="closeIcon" /> - </Close> - <Title> - <h3>폴더 삭제</h3> - <p>폴더명</p> - - - - + + + + closeIcon + + + <h3>폴더 삭제</h3> + <p>폴더명</p> + + + + ); }; -const Background = styled.div<{ $isVisible: string }>` - display: ${({ $isVisible }) => ($isVisible === "삭제" ? "block" : "none")}; +const Background = styled.div` + visibility: visible; z-index: 9999; position: fixed; top: 0; diff --git a/components/common/modals/EditNameModal.tsx b/components/common/modals/EditNameModal.tsx index d0f9a4d07..13a928ccc 100644 --- a/components/common/modals/EditNameModal.tsx +++ b/components/common/modals/EditNameModal.tsx @@ -1,21 +1,26 @@ +import { useRef } from "react"; import { styled } from "styled-components"; import closeIcon from "@/public/assets/icons/closeModal.png"; import Image from "next/image"; import { BlueButton } from "../BlueButton"; -import { CommonModalProps } from "@/constants/commonTypes"; +import { useModal } from "@/contexts/ModalContext"; +import useOutSideClick from "@/hooks/useOutSideClick"; -export const EditNameModal = ({ - isModalVisible, - setIsModalVisible, -}: CommonModalProps) => { - const handleCloseBtn = () => { - setIsModalVisible(""); - }; +export const EditNameModal = () => { + + const modal = useModal(); + const modalRef = useRef(null); + + const onCloseModal = () => { + modal.closeModal(); + } + + useOutSideClick({ ref: modalRef, callback: onCloseModal}); return ( - - - handleCloseBtn()}> + + + closeIcon 폴더 이름 변경 @@ -34,9 +39,7 @@ export const EditNameModal = ({ ); }; -const Background = styled.div<{ $isVisible: string }>` - display: ${({ $isVisible }) => - $isVisible === "이름 변경" ? "block" : "none"}; +const Background = styled.div` z-index: 9999; position: fixed; top: 0; diff --git a/components/common/modals/ModalPortalContent.tsx b/components/common/modals/ModalPortalContent.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/components/common/modals/PopOver.tsx b/components/common/modals/PopOver.tsx index 23b36449a..d5520f58a 100644 --- a/components/common/modals/PopOver.tsx +++ b/components/common/modals/PopOver.tsx @@ -1,47 +1,56 @@ import styled from "styled-components"; import { DeleteModal } from "@/components/common/modals/DeleteModal"; import { AddToFolder } from "@/components/common/modals/AddToFolder"; +import { useEffect, useRef } from "react"; type PopOverPropTypes = { $isPopOverVisible: boolean; setIsPopOverVisible: any; - $options: string[]; - $modalType: any; + popOverInfo: { + option: string; + callback: () => void; + } []; $top: string; $right: string; - $isModalVisible: string; - setIsModalVisible: any; }; export const PopOver = ({ $isPopOverVisible, - $options, - $modalType, + setIsPopOverVisible, + popOverInfo, $top, $right, - $isModalVisible, - setIsModalVisible, }: PopOverPropTypes) => { + + const popoverRef = useRef(null); + + useEffect (() => { + const handleClick = (e: MouseEvent) => { + if(popoverRef.current && !popoverRef.current.contains(e.target as Node)) { + setIsPopOverVisible(false); + } + } + + window.addEventListener('mousedown', handleClick); + + return ()=> window.removeEventListener('mousedown', handleClick); + }) + + + return ( <> - - - - {$options.map((option, index) => ( + + {popOverInfo.map((option) => ( ))} @@ -64,6 +73,7 @@ const MenuOptions = styled.div` display: ${({ $isVisible }) => ($isVisible ? "block" : "none")}; background-color: #fff; z-index: 1; + box-shadow: 3px 8px 15px -4px rgba(0,0,0,0.38); `; const Option = styled.p` diff --git a/components/common/modals/SharedModal.tsx b/components/common/modals/SharedModal.tsx index 867dab6fb..0f7a707d2 100644 --- a/components/common/modals/SharedModal.tsx +++ b/components/common/modals/SharedModal.tsx @@ -1,21 +1,26 @@ +import { useRef } from "react"; import styled from "styled-components"; import closeIcon from "@/public/assets/icons/closeModal.png"; import Image from "next/image"; -import { CommonModalProps } from "@/constants/commonTypes"; +import { useModal } from "@/contexts/ModalContext"; +import useOutSideClick from "@/hooks/useOutSideClick"; import { SOCIAL_ICONS } from "@/constants/socialIcon"; -export const SharedModal = ({ - isModalVisible, - setIsModalVisible, -}: CommonModalProps) => { - const handleCloseBtn = () => { - setIsModalVisible(""); - }; +export const SharedModal = () => { + + const modal = useModal(); + const modalRef = useRef(null); + + const onCloseModal = () => { + modal.closeModal(); + } + + useOutSideClick({ ref: modalRef, callback: onCloseModal}); return ( - - - handleCloseBtn()}> + + + closeIcon @@ -42,8 +47,7 @@ type BackgroundPropsType = { $backgroundColor?: string; }; -const Background = styled.div<BackgroundPropsType>` - display: ${({ $isVisible }) => ($isVisible === "공유" ? "block" : "none")}; +const Background = styled.div` z-index: 9999; position: fixed; top: 0; diff --git a/components/folder/FolderInput.tsx b/components/folder/FolderInput.tsx index fd057e3d9..183468af4 100644 --- a/components/folder/FolderInput.tsx +++ b/components/folder/FolderInput.tsx @@ -102,6 +102,7 @@ const InputBoxFixed = styled(InputBox)` const Input = styled.input` width: 100%; + height: min-content; border: none; margin-left: 12px; @@ -112,7 +113,7 @@ const Input = styled.input` &::placeholder { color: #9fa6b2; font-family: Pretendard; - font-size: 16px; + font-size: 14px; font-style: normal; font-weight: 400; line-height: 24px; diff --git a/components/folder/FolderTitle.tsx b/components/folder/FolderTitle.tsx index 403dcfa8b..6ca3a0dba 100644 --- a/components/folder/FolderTitle.tsx +++ b/components/folder/FolderTitle.tsx @@ -2,25 +2,39 @@ import styled from "styled-components"; import Share from "@/public/assets/icons/share.svg"; import Pen from "@/public/assets/icons/pen.svg"; import Trash from "@/public/assets/icons/trash.svg"; +import { useModal } from "@/contexts/ModalContext"; +import { SharedModal } from "../common/modals/SharedModal"; +import { EditNameModal } from "../common/modals/EditNameModal"; +import { DeleteModal } from "../common/modals/DeleteModal"; type FolderTitlePropsType = { titleName: string; - setIsModal: any; }; -const FolderTitle = ({ titleName, setIsModal }: FolderTitlePropsType) => { +const FolderTitle = ({ titleName }: FolderTitlePropsType) => { + const modal = useModal(); + const titles = [ { name: "공유", svg: <Share />, + callback: () => { + modal.openModal(<SharedModal />) + } }, { name: "이름 변경", svg: <Pen />, + callback: () => { + modal.openModal(<EditNameModal />) + } }, { name: "삭제", svg: <Trash />, + callback: () => { + modal.openModal(<DeleteModal />) + } }, ]; @@ -32,9 +46,7 @@ const FolderTitle = ({ titleName, setIsModal }: FolderTitlePropsType) => { {titles.map((title) => ( <Option key={title.name} - onClick={() => { - setIsModal(title.name); - }} + onClick={title.callback} > {title.svg} <OptionText>{title.name}</OptionText> diff --git a/components/folder/Menus.tsx b/components/folder/Menus.tsx index 7ff8423bd..e952c5c4c 100644 --- a/components/folder/Menus.tsx +++ b/components/folder/Menus.tsx @@ -4,17 +4,19 @@ import styled from "styled-components"; import Union from "@/public/assets/icons/Union.svg"; import { COLORS } from "../../constants/colors"; import { useGetPromise } from "@/hooks/useGetPromise"; +import { useModal } from "@/contexts/ModalContext"; +import { AddFolderModal } from "../common/modals/AddFolderModal"; type MenusPropsType = { changeTitle: any; changeID: any; - setIsVisible: any; }; -const Menus = ({ changeTitle, changeID, setIsVisible }: MenusPropsType) => { +const Menus = ({ changeTitle, changeID }: MenusPropsType) => { const [buttonColors, setButtonColors] = useState([]); const fetchedMenuData: any = useGetPromise(getFolderList); const menulists = fetchedMenuData?.data ?? []; + const modal = useModal(); useEffect(() => { const initialButtonColors = setAllWhiteButtonColor(); @@ -49,6 +51,10 @@ const Menus = ({ changeTitle, changeID, setIsVisible }: MenusPropsType) => { changeButtonColor(name); }; + const addFolderClickHandler = () => { + modal.openModal(<AddFolderModal />) + } + return ( <Container> <ButtonDiv> @@ -63,7 +69,7 @@ const Menus = ({ changeTitle, changeID, setIsVisible }: MenusPropsType) => { </Button> ))} </ButtonDiv> - <AddFolderDiv onClick={() => setIsVisible("폴더 추가")}> + <AddFolderDiv onClick={addFolderClickHandler}> <AddFolder>폴더 추가</AddFolder> <Union /> </AddFolderDiv> diff --git a/components/shared/SharedSection.tsx b/components/shared/SharedSection.tsx index 2128e8129..57ec4c5c3 100644 --- a/components/shared/SharedSection.tsx +++ b/components/shared/SharedSection.tsx @@ -1,4 +1,4 @@ -import icon_smile from "@/assets/icons/icon_smile.png"; +import icon_smile from "@/public/assets/icons/icon_smile.png"; import styled from "styled-components"; import { OwnerProps } from "@/constants/commonTypes"; diff --git a/contexts/ModalContext.tsx b/contexts/ModalContext.tsx new file mode 100644 index 000000000..bb697e9e3 --- /dev/null +++ b/contexts/ModalContext.tsx @@ -0,0 +1,44 @@ +import { createContext, useContext, useState } from "react"; + +type ModalProvider = { + isOpen: boolean; + openModal: (modal: React.ReactNode) => void; + closeModal: () => void; +} + +const ModalContext = createContext<ModalProvider | undefined>(undefined); + +export const useModal = () => { + const context = useContext(ModalContext); + if(!context) throw new Error ("modal error"); + return context; +}; + +export const ModalProvider = ({children}: {children: any}) => { + + const [isOpen, setIsOpen] = useState(false); + const [modalComponent, setModalComponent] = useState<React.ReactNode | null >(null) + const [modalStack, setModalStack] = useState<React.ReactNode[]>([]); + + const openModal = (modal: React.ReactNode) => { + setIsOpen(true); + setModalComponent(modal); + setModalStack((prevStack) => [...prevStack, modal]); + } + const closeModal = () => { + setIsOpen(false); + const prevStack = [...modalStack]; + prevStack.pop(); + const prevModal = prevStack[prevStack.length-1]; + setModalStack(prevStack); + setModalComponent(prevModal); + + } + + return ( + <ModalContext.Provider value={{isOpen, openModal, closeModal}}> + {children} + {modalComponent} + </ModalContext.Provider> + ) +} \ No newline at end of file diff --git a/hooks/useOutSideClick.ts b/hooks/useOutSideClick.ts new file mode 100644 index 000000000..57fe5c57a --- /dev/null +++ b/hooks/useOutSideClick.ts @@ -0,0 +1,22 @@ +import React, {useEffect} from 'react'; + +type UseOutSideClickType = { + ref: React.RefObject<HTMLDivElement>; + callback: Function; +} + +const useOutSideClick = ({ref, callback} : UseOutSideClickType) => { + const handleClick = (e: MouseEvent) => { + if(ref.current && !ref.current.contains(e.target as Node)) { + callback?.(); + } + }; + + useEffect(() => { + window.addEventListener('mousedown', handleClick); + + return () => window.removeEventListener('mousedown', handleClick); + }) +} + +export default useOutSideClick; \ No newline at end of file diff --git a/pages/Folder.tsx b/pages/Folder.tsx index 7cd2972f3..5ae9d494c 100644 --- a/pages/Folder.tsx +++ b/pages/Folder.tsx @@ -10,7 +10,6 @@ import Menus from "../components/folder/Menus"; import FolderTitle from "../components/folder/FolderTitle"; import { SharedModal } from "../components/common/modals/SharedModal"; import { EditNameModal } from "../components/common/modals/EditNameModal"; -import { DeleteModal } from "../components/common/modals/DeleteModal"; import { AddFolderModal } from "../components/common/modals/AddFolderModal"; import { COLORS } from "../constants/colors"; import { CommonFolderInfoProps } from "@/constants/commonTypes"; @@ -94,22 +93,6 @@ const Folder = () => { return ( <Container> - <SharedModal - isModalVisible={isModalVisible} - setIsModalVisible={setIsModalVisible} - /> - <EditNameModal - isModalVisible={isModalVisible} - setIsModalVisible={setIsModalVisible} - /> - <DeleteModal - isModalVisible={isModalVisible} - setIsModalVisible={setIsModalVisible} - /> - <AddFolderModal - isModalVisible={isModalVisible} - setIsModalVisible={setIsModalVisible} - /> <HeaderElement $positionval="static" /> <FolderInput setIsVisible={setIsModalVisible} @@ -124,14 +107,11 @@ const Folder = () => { <Menus changeTitle={setTitleName} changeID={setListId} - setIsVisible={setIsModalVisible} /> - <FolderTitle titleName={titleName} setIsModal={setIsModalVisible} /> + <FolderTitle titleName={titleName} /> {data[0] ? ( <FolderList items={data} - $isModalVisible={isModalVisible} - setIsModalVisible={setIsModalVisible} /> ) : ( <NoLinkMsg>저장된 링크가 없습니다.</NoLinkMsg> diff --git a/pages/ModalTest.tsx b/pages/ModalTest.tsx new file mode 100644 index 000000000..f2791903b --- /dev/null +++ b/pages/ModalTest.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { DeleteModal } from '@/components/common/modals/DeleteModal'; +import { useModal } from '@/contexts/ModalContext'; + + +const ModalTest = () => { + + const modal = useModal(); + + const buttonHandler = () => { + modal.openModal(<DeleteModal />); + } + + return ( + <div>ModalTest + <button onClick={buttonHandler}>모달 가져오기</button> + </div> + ) +} + +export default ModalTest; \ No newline at end of file diff --git a/pages/Shared.tsx b/pages/Shared.tsx index d79c20549..aed5b64f7 100644 --- a/pages/Shared.tsx +++ b/pages/Shared.tsx @@ -5,13 +5,10 @@ import FooterElement from "../components/common/FooterElement"; import SharedSection from "../components/shared/SharedSection"; import Input from "../components/common/Input"; import FolderList from "../components/common/FolderList"; -import { AddFolderModal } from "../components/common/modals/AddFolderModal"; -import { DeleteModal } from "@/components/common/modals/DeleteModal"; import { OwnerProps } from "@/constants/commonTypes"; export default function Shared() { const [searchInputValue, setSearchInputValue] = useState(""); - const [isModalVisible, setIsModalVisible] = useState(""); const [data, setData] = useState([]); const [folderName, setFolderName] = useState(""); const [owner, setOwner] = useState<OwnerProps>({ @@ -37,14 +34,6 @@ export default function Shared() { return ( <> - <DeleteModal - isModalVisible={isModalVisible} - setIsModalVisible={setIsModalVisible} - /> - <AddFolderModal - isModalVisible={isModalVisible} - setIsModalVisible={setIsModalVisible} - /> <HeaderElement $positionval="" /> <SharedSection folderName={folderName} owner={owner} /> <Input @@ -55,8 +44,6 @@ export default function Shared() { {data[0] ? ( <FolderList items={data} - $isModalVisible={isModalVisible} - setIsModalVisible={setIsModalVisible} /> ) : null} <FooterElement /> diff --git a/pages/Signin.tsx b/pages/Signin.tsx index 2a6be9537..9fc70725a 100644 --- a/pages/Signin.tsx +++ b/pages/Signin.tsx @@ -29,7 +29,7 @@ export default function Signin() { useEffect(() => { localStorage.remove("accssToken"); - // localStorage.get("accessToken") && location.assign("/Folder"); + localStorage.get("accessToken") && location.assign("/Folder"); }, []); diff --git a/pages/Signup.tsx b/pages/Signup.tsx index 0771dd08b..ade6162c9 100644 --- a/pages/Signup.tsx +++ b/pages/Signup.tsx @@ -33,11 +33,15 @@ export default function Signup() { let result; try { result = await postSignUp(postJsonValue); + } catch (error) { console.error(error); } - if(result.error) { + if(result.data) { + localStorage.save("accessToken", result.data.accessToken); + location.assign("/Folder"); + } else if(result.error) { toast.setText("회원가입에 실패하였습니다."); toast.setViewToast(true); } diff --git a/pages/_app.tsx b/pages/_app.tsx index 935b31fe2..1bd94c002 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -4,15 +4,17 @@ import "@/styles/globals.css"; import { ToastProvider } from "@/contexts/ToastContext"; import Portal from "@/components/common/Portal"; import ToastPortalContent from "@/components/common/ToastPortalContent"; - +import { ModalProvider } from "@/contexts/ModalContext"; export default function App({ Component, pageProps }: AppProps) { return ( <ToastProvider> - <Component {...pageProps}/> - <Portal> - <ToastPortalContent /> - </Portal> + <ModalProvider> + <Component {...pageProps}/> + <Portal> + <ToastPortalContent /> + </Portal> + </ModalProvider> </ToastProvider> ); } From 0098a38b3fc9f8cf6f7474bc9681f668d86f0c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=86=E1=85=B5=E1=86=AB?= =?UTF-8?q?=E1=84=8C=E1=85=A5=E1=86=BC?= <minjeong9919@naver.com> Date: Tue, 14 May 2024 10:19:16 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20=EC=A0=84=EB=B0=98=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/SignInUp/EmailPwdInput.tsx | 82 +----------------------- components/common/FolderItem.tsx | 4 +- components/common/ToastPortalContent.tsx | 1 + pages/Folder.tsx | 20 +++--- utils/calculator.ts | 2 +- utils/commonSigninupFunc.ts | 10 --- utils/validation.ts | 2 +- 7 files changed, 17 insertions(+), 104 deletions(-) delete mode 100644 utils/commonSigninupFunc.ts diff --git a/components/SignInUp/EmailPwdInput.tsx b/components/SignInUp/EmailPwdInput.tsx index bc029d193..479ed8b9b 100644 --- a/components/SignInUp/EmailPwdInput.tsx +++ b/components/SignInUp/EmailPwdInput.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect, ChangeEvent, KeyboardEvent } from "react"; +import { useState, useEffect } from "react"; import styled from "styled-components"; import Image from "next/image"; import eyeoff from "@/public/assets/icons/eye-off.png"; @@ -42,86 +42,6 @@ const EmailPwdInput = ({ },[inputErrorInfo]) - // const setInputStatusAndErrorMessage = (status: "valid" | ErrorType) => { - // status === "valid" - // ? setInputStatus(INPUT_STATUS_VALUE.default) - // : (setInputStatus(INPUT_STATUS_VALUE.error), - // setInputErrorMessage(status.message)); - // }; - - // const onInputFocusOutHandler = (e: ChangeEvent<HTMLInputElement>) => { - // const inputValue = e.target.value; - - // switch (type) { - // case "email": - // emailInputFocusOutHandler(inputValue); - // break; - // case "password": { - // passwordInputFocusOutHandler(inputValue); - // break; - // } - // case "password2": - // passwordConfirmInputFocusOutHandler(inputValue); - // break; - // default: - // null; - // } - // }; - - // const emailInputFocusOutHandler = async (email: string) => { - // const emailInputStatus = emailInputValidationcheck(email); - // setInputStatusAndErrorMessage(emailInputStatus); - // if (emailInputStatus === "valid") { - // setIsEmailValid?.(true); - // if (signInStatus) { - // const isDuplicateEmail = await emailDuplicationCheck(email); - // isDuplicateEmail && - // setInputStatusAndErrorMessage(INPUT_STATUS.inUseEmail); - // } - // } else { - // setIsEmailValid?.(false); - // } - // }; - - // const passwordInputFocusOutHandler = async (password: string) => { - // let passwordInputStatus; - // if (loginStatus) { - // passwordInputStatus = loginPasswordInputValidationcheck(password); - // } else { - // passwordInputStatus = signInPasswordInputValidationcheck(password); - // } - // setInputStatusAndErrorMessage(passwordInputStatus); - // passwordInputStatus === "valid" - // ? setIsPasswordValid?.(true) - // : setIsPasswordValid?.(false); - // }; - - // const passwordConfirmInputFocusOutHandler = async (password2: string) => { - // const passwordInputStatus = signInPasswordInputValidationcheck( - // passwordValue, - // password2 - // ); - // setInputStatusAndErrorMessage(passwordInputStatus); - // passwordInputStatus === "valid" - // ? setIsPasswordConfirmValid?.(true) - // : setIsPasswordConfirmValid?.(false); - // }; - - // useEffect(() => { - // if (loginStatus === "fail") { - // valueType === "email" - // ? (setInputStatus(INPUT_STATUS_VALUE.error), - // setInputErrorMessage(ERROR_MESSAGE.email.check)) - // : (setInputStatus(INPUT_STATUS_VALUE.error), - // setInputErrorMessage(ERROR_MESSAGE.password.check)); - // } - // }, [loginStatus]); - - // const onInputFocusHandler = () => { - // setInputStatus("writing"); - // }; - - return ( <InputDiv> <p>{title}</p> diff --git a/components/common/FolderItem.tsx b/components/common/FolderItem.tsx index 03f87f30c..70f3b1b4f 100644 --- a/components/common/FolderItem.tsx +++ b/components/common/FolderItem.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; import { useState } from "react"; -import { CalcTime } from "@/utils/calculator"; +import { calcTime } from "@/utils/calculator"; import Star from "@/public/assets/icons/card_star.svg"; import Kebab from "@/public/assets/icons/kebab.svg"; import logo from "@/public/assets/icons/logo.png"; @@ -25,7 +25,7 @@ function FolderItem({ const [isPopOverVisible, setIsPopOverVisible] = useState(false); const createdAtTime: string = String(createdAt ?? created_at); - const time = CalcTime(createdAtTime); + const time = calcTime(createdAtTime); const img_src = image_source || imageSource; const modal = useModal(); diff --git a/components/common/ToastPortalContent.tsx b/components/common/ToastPortalContent.tsx index 6c7c7d9dc..9fbcd3cfb 100644 --- a/components/common/ToastPortalContent.tsx +++ b/components/common/ToastPortalContent.tsx @@ -1,3 +1,4 @@ + const ToastPortalContent = ({children}: {children?:any}) => { return ( children diff --git a/pages/Folder.tsx b/pages/Folder.tsx index 5ae9d494c..69e325d31 100644 --- a/pages/Folder.tsx +++ b/pages/Folder.tsx @@ -26,16 +26,18 @@ const Folder = () => { const [isModalVisible, setIsModalVisible] = useState(""); const [searchInputValue, setSearchInputValue] = useState(""); + const fetchData = async () => { + try { + const response = await getAllLinkData(listId); + const result = await response.data; + setData(result); + } catch (error) { + console.error(error); + } + }; + fetchData(); + useEffect(() => { - const fetchData = async () => { - try { - const response = await getAllLinkData(listId); - const result = await response.data; - setData(result); - } catch (error) { - console.error(error); - } - }; fetchData(); }, [listId]); diff --git a/utils/calculator.ts b/utils/calculator.ts index 8af6d0e8e..b843dc661 100644 --- a/utils/calculator.ts +++ b/utils/calculator.ts @@ -31,7 +31,7 @@ function timeToString(time: number) { return `${diffYear} years ago`; } -export function CalcTime(time: string) { +export function calcTime(time: string) { const nowTime = Date.now(); const writingTime = Date.parse(time); const diffTime = nowTime - writingTime; diff --git a/utils/commonSigninupFunc.ts b/utils/commonSigninupFunc.ts deleted file mode 100644 index fefc2cee7..000000000 --- a/utils/commonSigninupFunc.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const changeInputBorderColor = (status: string | undefined) => { - switch (status) { - case "default": - return "var(--Grey_300)"; - case "writing": - return "var(--Primary)"; - default: - return "var(--Red)"; - } -}; diff --git a/utils/validation.ts b/utils/validation.ts index 3b82295d5..ebecdfd16 100644 --- a/utils/validation.ts +++ b/utils/validation.ts @@ -3,7 +3,7 @@ import { postCheckDuplicationEmail } from "@/api/api"; // 이메일 유효성 검사 function emailCheck(email_address: string) { - let email_regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/i; + const email_regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/i; return Boolean(email_regex.test(email_address)); }