From 22703cae37c962874d442d3b8a2d31f8f38493bc Mon Sep 17 00:00:00 2001 From: ksm Date: Tue, 3 Dec 2024 10:39:55 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[refactor]:=20sprint10=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Icons/BackIcon.tsx | 24 ++++++ components/Icons/EmptyIcon.tsx | 21 +++++ components/Icons/HeartIcon.tsx | 26 ++++++ components/Icons/InquiryEmptyIcon.tsx | 115 ++++++++++++++++++++++++++ components/Icons/MedalIcon.tsx | 26 ++++++ components/Icons/PlusIcon.tsx | 31 +++++++ components/Icons/ProfileIcon.tsx | 64 ++++++++++++++ components/Icons/SearchIcon.tsx | 23 ++++++ components/Icons/ToggleIcon.tsx | 22 +++++ components/addBoard/ImgFileInput.tsx | 32 ++----- components/addItem/ImgFileInput.tsx | 22 +---- components/addItem/PriceInput.tsx | 2 +- components/articles/Article.tsx | 101 ++-------------------- components/articles/ArticleList.tsx | 2 +- components/articles/ImageArticle.tsx | 7 +- components/common/Header.tsx | 59 +------------ components/detail/DetailArticle.tsx | 72 +--------------- components/detail/DetailInquiry.tsx | 59 +------------ components/detail/DetailProduct.tsx | 87 +------------------ components/detail/DropDownInquiry.tsx | 7 +- components/detail/InquiryEmpty.tsx | 98 +--------------------- components/items/ImageProduct.tsx | 7 +- components/items/Product.tsx | 14 +--- components/items/Search.tsx | 8 +- pages/api/articleApi.ts | 4 +- pages/boards/[id].tsx | 19 +---- pages/index.tsx | 4 +- pages/items/[id].tsx | 16 +--- utils/formatDate.ts | 2 +- 29 files changed, 408 insertions(+), 566 deletions(-) create mode 100644 components/Icons/BackIcon.tsx create mode 100644 components/Icons/EmptyIcon.tsx create mode 100644 components/Icons/HeartIcon.tsx create mode 100644 components/Icons/InquiryEmptyIcon.tsx create mode 100644 components/Icons/MedalIcon.tsx create mode 100644 components/Icons/PlusIcon.tsx create mode 100644 components/Icons/ProfileIcon.tsx create mode 100644 components/Icons/SearchIcon.tsx create mode 100644 components/Icons/ToggleIcon.tsx diff --git a/components/Icons/BackIcon.tsx b/components/Icons/BackIcon.tsx new file mode 100644 index 000000000..ddbb5512b --- /dev/null +++ b/components/Icons/BackIcon.tsx @@ -0,0 +1,24 @@ +interface IconProps { + width?: string; + height?: string; +} + +function BackIcon({ width, height }: IconProps) { + return ( + + + + + ); +} + +export default BackIcon; diff --git a/components/Icons/EmptyIcon.tsx b/components/Icons/EmptyIcon.tsx new file mode 100644 index 000000000..27786f822 --- /dev/null +++ b/components/Icons/EmptyIcon.tsx @@ -0,0 +1,21 @@ +interface IconProps { + width?: string; + height?: string; +} + +function EmptyIcon({ width, height }: IconProps) { + return ( + + + + + + ); +} + +export default EmptyIcon; diff --git a/components/Icons/HeartIcon.tsx b/components/Icons/HeartIcon.tsx new file mode 100644 index 000000000..1e93f477e --- /dev/null +++ b/components/Icons/HeartIcon.tsx @@ -0,0 +1,26 @@ +interface IconProps { + width?: string; + height?: string; + isFavorite?: boolean; +} + +function HeartIcon({ width, height, isFavorite = false }: IconProps) { + return ( + + + + ); +} + +export default HeartIcon; diff --git a/components/Icons/InquiryEmptyIcon.tsx b/components/Icons/InquiryEmptyIcon.tsx new file mode 100644 index 000000000..b3762b4b0 --- /dev/null +++ b/components/Icons/InquiryEmptyIcon.tsx @@ -0,0 +1,115 @@ +interface IconProps { + width?: string; + height?: string; + isComment?: boolean; +} + +function InquiryEmptyIcon({ width, height, isComment = false }: IconProps) { + return ( + <> + {isComment ? ( + + + + + + + + + + + ) : ( + + + + + + + + + + + + + + + + + + )} + + ); +} + +export default InquiryEmptyIcon; diff --git a/components/Icons/MedalIcon.tsx b/components/Icons/MedalIcon.tsx new file mode 100644 index 000000000..3c7efdae0 --- /dev/null +++ b/components/Icons/MedalIcon.tsx @@ -0,0 +1,26 @@ +interface IconProps { + width?: string; + height?: string; +} + +function MedalIcon({ width, height }: IconProps) { + return ( + + + + + ); +} + +export default MedalIcon; diff --git a/components/Icons/PlusIcon.tsx b/components/Icons/PlusIcon.tsx new file mode 100644 index 000000000..643268f1a --- /dev/null +++ b/components/Icons/PlusIcon.tsx @@ -0,0 +1,31 @@ +interface IconProps { + width?: string; + height?: string; +} + +function PlusIcon({ width, height }: IconProps) { + return ( + + + + + ); +} + +export default PlusIcon; diff --git a/components/Icons/ProfileIcon.tsx b/components/Icons/ProfileIcon.tsx new file mode 100644 index 000000000..3d8ad1f5b --- /dev/null +++ b/components/Icons/ProfileIcon.tsx @@ -0,0 +1,64 @@ +interface IconProps { + width?: string; + height?: string; +} + +function ProfileIcon({ width, height }: IconProps) { + return ( + + + + + + + + + + + + + + + + + + + + + + ); +} + +export default ProfileIcon; diff --git a/components/Icons/SearchIcon.tsx b/components/Icons/SearchIcon.tsx new file mode 100644 index 000000000..1708b2f0e --- /dev/null +++ b/components/Icons/SearchIcon.tsx @@ -0,0 +1,23 @@ +interface IconProps { + width?: string; + height?: string; +} + +function SearchIcon({ width, height }: IconProps) { + return ( + + + + ); +} + +export default SearchIcon; diff --git a/components/Icons/ToggleIcon.tsx b/components/Icons/ToggleIcon.tsx new file mode 100644 index 000000000..2ae9f5390 --- /dev/null +++ b/components/Icons/ToggleIcon.tsx @@ -0,0 +1,22 @@ +interface IconProps { + width?: string; + height?: string; +} + +function ToggleIcon({ width, height }: IconProps) { + return ( + + + + + + ); +} + +export default ToggleIcon; diff --git a/components/addBoard/ImgFileInput.tsx b/components/addBoard/ImgFileInput.tsx index ecfc3ab51..cea6f862d 100644 --- a/components/addBoard/ImgFileInput.tsx +++ b/components/addBoard/ImgFileInput.tsx @@ -1,13 +1,19 @@ import { FormInputInterface } from "@/types/addBoard"; import Image from "next/image"; import { ReactNode, useEffect, useRef, useState } from "react"; -import { FieldValues, UseFormRegister, UseFormWatch } from "react-hook-form"; +import { + FieldValues, + UseFormRegister, + UseFormSetValue, + UseFormWatch, +} from "react-hook-form"; +import PlusIcon from "../Icons/PlusIcon"; interface ImgFileInputProps { name: keyof FormInputInterface; register: UseFormRegister; watch: UseFormWatch; - setValue: any; + setValue: UseFormSetValue; children: ReactNode; } @@ -61,7 +67,6 @@ function ImgFileInput({ register(name).ref(el); }} /> - {/* className='sr-only' */}
diff --git a/components/addItem/ImgFileInput.tsx b/components/addItem/ImgFileInput.tsx index 80b6f7be8..376bbd443 100644 --- a/components/addItem/ImgFileInput.tsx +++ b/components/addItem/ImgFileInput.tsx @@ -9,6 +9,7 @@ import { useRef, useState, } from "react"; +import PlusIcon from "../Icons/PlusIcon"; interface ImgFileInputProps { images: string[]; @@ -95,26 +96,7 @@ function ImgFileInput({ onClick={handleClick} > - - - - + 이미지 등록 diff --git a/components/addItem/PriceInput.tsx b/components/addItem/PriceInput.tsx index aa48b386c..17eeec6b7 100644 --- a/components/addItem/PriceInput.tsx +++ b/components/addItem/PriceInput.tsx @@ -22,7 +22,7 @@ function PriceInput({ const priceValue = formatToPrice(target.value); const regExp = /^\d*$/; // 숫자만 입력 가능하게 하기 위한 정규식 if (regExp.test(priceValue)) { - dispatch({ type: "SET_PRICE", payload: +priceValue }); + dispatch({ type: "SET_PRICE", payload: Number(priceValue) }); } }; return ( diff --git a/components/articles/Article.tsx b/components/articles/Article.tsx index 9a4051502..a1133e22d 100644 --- a/components/articles/Article.tsx +++ b/components/articles/Article.tsx @@ -3,6 +3,9 @@ import { formatDate } from "@/utils/formatDate"; import ImageArticle from "./ImageArticle"; import Link from "next/link"; import clsx from "clsx"; +import MedalIcon from "../Icons/MedalIcon"; +import HeartIcon from "../Icons/HeartIcon"; +import ProfileIcon from "../Icons/ProfileIcon"; interface ArticleProps { isBest?: boolean; @@ -17,21 +20,7 @@ function Article({ isBest = false, article }: ArticleProps) { {isBest && (
- - - - + Best
@@ -46,17 +35,7 @@ function Article({ isBest = false, article }: ArticleProps) {
{writer.nickname}
- - - + {likeCount}
@@ -67,80 +46,14 @@ function Article({ isBest = false, article }: ArticleProps) {
- - - - - - - - - - - - - - - - - - - - - +
{writer.nickname}
{formatDate(updatedAt)}
- - - + {likeCount}
diff --git a/components/articles/ArticleList.tsx b/components/articles/ArticleList.tsx index 48d91addc..ba11a9966 100644 --- a/components/articles/ArticleList.tsx +++ b/components/articles/ArticleList.tsx @@ -41,7 +41,7 @@ function ArticleList() { }).then(({ list, totalCount }) => { // 서버의 더 요청할 데이터가 없으면 스크롤 막기 위해 분기처리 const pageLimit = Math.ceil(totalCount / productsPerPage); - if (pageLimit <= +page) setHasMoreData(false); + if (pageLimit <= Number(page)) setHasMoreData(false); setArticles((prev) => (page === "1" ? list : [...prev, ...list])); setIsInitial(false); }); diff --git a/components/articles/ImageArticle.tsx b/components/articles/ImageArticle.tsx index 8ebedbb72..919c70a81 100644 --- a/components/articles/ImageArticle.tsx +++ b/components/articles/ImageArticle.tsx @@ -1,5 +1,6 @@ import Image from "next/image"; import { useState } from "react"; +import EmptyIcon from "../Icons/EmptyIcon"; interface ImageProps { image: string; @@ -13,11 +14,7 @@ function ImageArticle({ image, name }: ImageProps) { return (
{imageRenderError || !image ? ( - - - - - + ) : ( - - - - - - - - - - - - - - - - - - - - - +
) : ( diff --git a/components/detail/DetailArticle.tsx b/components/detail/DetailArticle.tsx index cb027f41f..c23944d63 100644 --- a/components/detail/DetailArticle.tsx +++ b/components/detail/DetailArticle.tsx @@ -1,5 +1,7 @@ import { ArticleInterface } from "@/types/article"; import { formatDate } from "@/utils/formatDate"; +import ProfileIcon from "../Icons/ProfileIcon"; +import HeartIcon from "../Icons/HeartIcon"; function DetailArticle({ id, @@ -17,80 +19,14 @@ function DetailArticle({
- - - - - - - - - - - - - - - - - - - - - +
{writer.nickname}
{formatDate(updatedAt)}
- - - +
{likeCount}
diff --git a/components/detail/DetailInquiry.tsx b/components/detail/DetailInquiry.tsx index 598a2b8c2..5140312d6 100644 --- a/components/detail/DetailInquiry.tsx +++ b/components/detail/DetailInquiry.tsx @@ -3,6 +3,7 @@ import DropDownInquiry from "./DropDownInquiry"; import { updateComment, updateCommentInterface } from "@/pages/api/productApi"; import { calculateGapTime } from "@/utils/formatDate"; import Image from "next/image"; +import ProfileIcon from "../Icons/ProfileIcon"; interface DetailInquiryProps { id: string; @@ -59,63 +60,7 @@ function DetailInquiry({ id, content, writer, updatedAt }: DetailInquiryProps) { {writer.image ? ( {writer.nickname} ) : ( - - - - - - - - - - - - - - - - - - - - - + )}
diff --git a/components/detail/DetailProduct.tsx b/components/detail/DetailProduct.tsx index 0930c3777..bdb6e010c 100644 --- a/components/detail/DetailProduct.tsx +++ b/components/detail/DetailProduct.tsx @@ -1,6 +1,8 @@ import { formatPriceToKRW } from "@/utils/formatPrice"; import { formatDate } from "@/utils/formatDate"; import ImageProduct from "../items/ImageProduct"; +import ProfileIcon from "../Icons/ProfileIcon"; +import HeartIcon from "../Icons/HeartIcon"; interface DetailProductProps { name: string; @@ -48,63 +50,7 @@ function DetailProduct({
- - - - - - - - - - - - - - - - - - - - - +
{ownerNickname}
@@ -113,32 +59,7 @@ function DetailProduct({
diff --git a/components/detail/DropDownInquiry.tsx b/components/detail/DropDownInquiry.tsx index 19fe5171e..013ad0425 100644 --- a/components/detail/DropDownInquiry.tsx +++ b/components/detail/DropDownInquiry.tsx @@ -1,5 +1,6 @@ import Image from "next/image"; import { useState } from "react"; +import ToggleIcon from "../Icons/ToggleIcon"; interface DropDownInquiryProps { setIsEditting: (value: boolean) => void; @@ -23,11 +24,7 @@ function DropDownInquiry({ setIsEditting }: DropDownInquiryProps) { className='btn-open-menu' onClick={() => setIsOpen((prev) => !prev)} > - - - - - + {isOpen && (
diff --git a/components/detail/InquiryEmpty.tsx b/components/detail/InquiryEmpty.tsx index 57a701f8c..9ec396357 100644 --- a/components/detail/InquiryEmpty.tsx +++ b/components/detail/InquiryEmpty.tsx @@ -1,3 +1,5 @@ +import InquiryEmptyIcon from "../Icons/InquiryEmptyIcon"; + interface InquiryEmptyProps { isArticle?: boolean; } @@ -8,30 +10,7 @@ function InquiryEmpty({ isArticle = false }: InquiryEmptyProps) { {isArticle ? ( <>
- - - - - - - - - - +
아직 댓글이 없어요, @@ -42,76 +21,7 @@ function InquiryEmpty({ isArticle = false }: InquiryEmptyProps) { ) : ( <>
- - - - - - - - - - - - - - - - - +
아직 문의가 없어요 diff --git a/components/items/ImageProduct.tsx b/components/items/ImageProduct.tsx index 6b669bcf6..e2670ba7d 100644 --- a/components/items/ImageProduct.tsx +++ b/components/items/ImageProduct.tsx @@ -1,5 +1,6 @@ import Image from "next/image"; import { useState } from "react"; +import EmptyIcon from "../Icons/EmptyIcon"; interface ImageProps { images: string[]; @@ -13,11 +14,7 @@ function ImageProduct({ images, name }: ImageProps) { return (
{imageRenderError || !image ? ( - - - - - + ) : ( {formattedPrice}
- - - +
{favoriteCount}
diff --git a/components/items/Search.tsx b/components/items/Search.tsx index e1937b96d..64fb354ca 100644 --- a/components/items/Search.tsx +++ b/components/items/Search.tsx @@ -1,5 +1,6 @@ import Image from "next/image"; import { ChangeEvent } from "react"; +import SearchIcon from "../Icons/SearchIcon"; interface SearchProps { setKeyword: (value: string) => void; @@ -11,12 +12,7 @@ function Search({ setKeyword }: SearchProps) { return (
- - - +
{ } }; -const SetTokensToLocalStorage = async () => { +const setTokensToLocalStorage = async () => { try { const response = await fetch( `${process.env.NEXT_PUBLIC_SERVER_URL}/auth/signIn`, @@ -233,5 +233,5 @@ export { fetchInquiryById, postArticle, postArticleComment, - SetTokensToLocalStorage, + setTokensToLocalStorage, }; diff --git a/pages/boards/[id].tsx b/pages/boards/[id].tsx index 2861ddf7b..943f00f5b 100644 --- a/pages/boards/[id].tsx +++ b/pages/boards/[id].tsx @@ -12,6 +12,7 @@ import { ArticleCommentInterface, ArticleInterface } from "@/types/article"; import PrimaryButton from "@/components/common/PrimaryButton"; import DetailInquiry from "@/components/detail/DetailInquiry"; import InquiryEmpty from "@/components/detail/InquiryEmpty"; +import BackIcon from "@/components/Icons/BackIcon"; const INITIAL_DETAILS: ArticleInterface = { id: 0, @@ -69,7 +70,7 @@ function Detail() { throw new Error("Id is null"); } const response = await postArticleComment({ - id: +id, + id: Number(id), content: newComment, }); setComments((prev) => ({ @@ -153,21 +154,7 @@ function Detail() { 목록으로 돌아가기 - {/* Sprint10에 추가 예정 */} - - - - +
diff --git a/pages/index.tsx b/pages/index.tsx index a803c0dd0..0f6c3fc54 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -3,11 +3,11 @@ import Link from "next/link"; import Footer from "@/components/common/Footer"; import Image from "next/image"; import { useEffect } from "react"; -import { SetTokensToLocalStorage } from "./api/articleApi"; +import { setTokensToLocalStorage } from "./api/articleApi"; export default function Home() { useEffect(() => { - SetTokensToLocalStorage(); + setTokensToLocalStorage(); }, []); return ( <> diff --git a/pages/items/[id].tsx b/pages/items/[id].tsx index 6f2a0eb0b..ad9dc4135 100644 --- a/pages/items/[id].tsx +++ b/pages/items/[id].tsx @@ -11,6 +11,7 @@ import DetailInquiry from "@/components/detail/DetailInquiry"; import InquiryEmpty from "@/components/detail/InquiryEmpty"; import Link from "next/link"; import { useRouter } from "next/router"; +import BackIcon from "@/components/Icons/BackIcon"; interface InputState { name: string; @@ -178,20 +179,7 @@ function Detail() { 목록으로 돌아가기 - - - - +
diff --git a/utils/formatDate.ts b/utils/formatDate.ts index a8424dbb0..812b03ba0 100644 --- a/utils/formatDate.ts +++ b/utils/formatDate.ts @@ -11,7 +11,7 @@ const calculateGapTime = (date: string) => { const currentDate = new Date(); const targetDate = new Date(date); const differenceInMinutes = Math.floor( - (+currentDate - +targetDate) / (1000 * 60) + (Number(currentDate) - Number(targetDate)) / (1000 * 60) ); const differenceInHour = Math.floor(differenceInMinutes / 60); const differenceInDays = Math.floor(differenceInHour / 24); From 8ecdda7c015ff5708e32ba1e1b15d7486f825119 Mon Sep 17 00:00:00 2001 From: ksm Date: Tue, 3 Dec 2024 19:57:24 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[refactor]:=20Form=20reducer=EC=97=90?= =?UTF-8?q?=EC=84=9C=20react-hook-form=EC=9C=BC=EB=A1=9C=20=EB=A7=88?= =?UTF-8?q?=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/auth/EmailInput.tsx | 25 ++--- components/auth/Form.tsx | 7 +- components/auth/NickNameInput.tsx | 26 ++---- components/auth/PassWordInput.tsx | 27 ++---- components/auth/PassWordInputConfirm.tsx | 32 ++----- pages/api/articleApi.ts | 34 ------- pages/index.tsx | 5 - pages/login.tsx | 58 +++++++----- pages/signup.tsx | 111 ++++++++++++++--------- styles/css/style.css | 1 - styles/scss/pages/_auth.scss | 1 - types/auth.ts | 13 +++ 12 files changed, 148 insertions(+), 192 deletions(-) create mode 100644 types/auth.ts diff --git a/components/auth/EmailInput.tsx b/components/auth/EmailInput.tsx index 190a55987..4d39a6ae8 100644 --- a/components/auth/EmailInput.tsx +++ b/components/auth/EmailInput.tsx @@ -1,34 +1,21 @@ -import validateField from "@/hooks/validateFied"; -import { AuthFormAction, AuthInputState } from "@/types/authForm"; -import { ChangeEvent, Dispatch, FocusEvent } from "react"; -import useInputHandler from "../../hooks/useInputHandler"; +import { UseFormRegisterReturn } from "react-hook-form"; interface EmailInputInterface { - state: AuthInputState; - handleValue: Dispatch; + register: UseFormRegisterReturn; + errorMessage?: string; } -function EmailInput({ state, handleValue }: EmailInputInterface) { - const { value, isValid, errorMessage, hasFocused } = state; - const { handleChange, handleBlur } = useInputHandler({ - field: "email", - handleValue, - }); +function EmailInput({ register, errorMessage }: EmailInputInterface) { return (
- {errorMessage} + {errorMessage && {errorMessage}}
); } diff --git a/components/auth/Form.tsx b/components/auth/Form.tsx index f5f2c1996..543788db0 100644 --- a/components/auth/Form.tsx +++ b/components/auth/Form.tsx @@ -1,3 +1,4 @@ +import { AuthFormState } from "@/types/authForm"; import Image from "next/image"; import Link from "next/link"; import { FormEvent, PropsWithChildren, ReactNode } from "react"; @@ -8,16 +9,14 @@ interface FormInterface { formType: FormType; isValid: boolean; children: ReactNode; + handleSubmit: () => void; } -const handleSubmit = (e: FormEvent) => { - e.preventDefault(); -}; - function Form({ children, formType, isValid = false, + handleSubmit, }: PropsWithChildren) { return (
diff --git a/components/auth/NickNameInput.tsx b/components/auth/NickNameInput.tsx index af0eb2867..cb8c00018 100644 --- a/components/auth/NickNameInput.tsx +++ b/components/auth/NickNameInput.tsx @@ -1,35 +1,21 @@ -import validateField from "@/hooks/validateFied"; -import { AuthFormAction, AuthInputState } from "@/types/authForm"; -import { ChangeEvent, Dispatch, FocusEvent } from "react"; -import useInputHandler from "../../hooks/useInputHandler"; +import { UseFormRegisterReturn } from "react-hook-form"; interface PasswordInputInterface { - state: AuthInputState; - handleValue: Dispatch; + register: UseFormRegisterReturn; + errorMessage?: string; } -function NickNameInput({ state, handleValue }: PasswordInputInterface) { - const { value, isValid, errorMessage, hasFocused } = state; - const { handleChange, handleBlur } = useInputHandler({ - field: "nickname", - handleValue, - }); - +function NickNameInput({ register, errorMessage }: PasswordInputInterface) { return (
- {errorMessage} + {errorMessage && {errorMessage}}
); } diff --git a/components/auth/PassWordInput.tsx b/components/auth/PassWordInput.tsx index de5ea2471..4a873690b 100644 --- a/components/auth/PassWordInput.tsx +++ b/components/auth/PassWordInput.tsx @@ -1,21 +1,12 @@ import usePasswordVisibility from "@/hooks/usePassWordVisivility"; -import validateField from "@/hooks/validateFied"; -import { AuthFormAction, AuthInputState } from "@/types/authForm"; -import { ChangeEvent, Dispatch, FocusEvent } from "react"; -import useInputHandler from "../../hooks/useInputHandler"; -import clsx from "clsx"; +import { UseFormRegisterReturn } from "react-hook-form"; interface PasswordInputInterface { - state: AuthInputState; - handleValue: Dispatch; + register: UseFormRegisterReturn; + errorMessage?: string; } -function PassWordInput({ state, handleValue }: PasswordInputInterface) { - const { value, isValid, errorMessage, hasFocused } = state; - const { handleChange, handleBlur } = useInputHandler({ - field: "password", - handleValue, - }); +function PassWordInput({ register, errorMessage }: PasswordInputInterface) { const { inputType, toggleVisibility } = usePasswordVisibility(); return ( @@ -24,15 +15,11 @@ function PassWordInput({ state, handleValue }: PasswordInputInterface) { - {errorMessage} + {errorMessage && {errorMessage}}
- {isLogin ? ( + {hasToken ? (
- setIsOpen((prev) => !prev)} > - + {isOpen && ( + + )} +
) : ( diff --git a/pages/api/authApi.ts b/pages/api/authApi.ts new file mode 100644 index 000000000..53496822b --- /dev/null +++ b/pages/api/authApi.ts @@ -0,0 +1,36 @@ +import { LoginInterface } from "@/types/auth"; + +const LoginAndSetToken = async ({ email, password }: LoginInterface) => { + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_SERVER_URL}/auth/signIn`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: email, + password: password, + }), + } + ); + + if (response.ok) { + const { accessToken, refreshToken } = await response.json(); + + // localStorage에 토큰 저장 + localStorage.setItem("accessToken", accessToken); + localStorage.setItem("refreshToken", refreshToken); + + console.log("토큰이 성공적으로 설정되었습니다."); + } else { + throw new Error("로그인 실패: 서버에서 인증 정보를 확인하세요."); + } + } catch (error) { + console.error("초기 로그인 요청 실패:", error); + throw error; + } +}; + +export { LoginAndSetToken }; diff --git a/pages/login.tsx b/pages/login.tsx index 4089dc4bf..04a0d8bd1 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -6,6 +6,7 @@ import Link from "next/link"; import { SubmitHandler, useForm } from "react-hook-form"; import { LoginAndSetToken } from "./api/authApi"; import { LoginInterface } from "@/types/auth"; +import { useRouter } from "next/router"; const INITIAL_FORM_STATE: LoginInterface = { email: "", @@ -13,6 +14,7 @@ const INITIAL_FORM_STATE: LoginInterface = { }; function Login() { + const router = useRouter(); const { register, handleSubmit, @@ -26,7 +28,8 @@ function Login() { email, password, }) => { - console.log(email, password); + await LoginAndSetToken({ email, password }); + router.push("/"); }; return ( diff --git a/styles/css/style.css b/styles/css/style.css index 597ca7c54..688e40659 100644 --- a/styles/css/style.css +++ b/styles/css/style.css @@ -474,6 +474,7 @@ template { --gray600: #4b5563; --gray500: #6b7280; --gray400: #9ca3af; + --gray300: #d1d5db; --gray200: #e5e7eb; --gray100: #f3f4f6; --gray50: #f9fafb; @@ -577,10 +578,25 @@ header .login-area .content-link a + a { header .login-area .content-link a.active { color: var(--primary100); } -header .login-area .link-profile { +header .login-area .btn-profile { position: relative; width: 4rem; height: 4rem; + border: 0; + background-color: transparent; +} +header .login-area .btn-profile .btn-logout { + position: absolute; + bottom: -6rem; + right: 0; + width: 13.9rem; + padding: 1.6rem 0; + border: 0.1rem solid var(--gray300); + border-radius: 0.8rem; + background-color: var(--white); + font-size: 1.6rem; + line-height: 1.9rem; + color: var(--gray500); } header .link-login { display: flex; diff --git a/styles/scss/components/_header.scss b/styles/scss/components/_header.scss index c2d577573..58b75519e 100644 --- a/styles/scss/components/_header.scss +++ b/styles/scss/components/_header.scss @@ -45,10 +45,25 @@ header { } } } - .link-profile { + .btn-profile { position: relative; width: 4rem; height: 4rem; + border: 0; + background-color: transparent; + .btn-logout { + position: absolute; + bottom: -6rem; + right: 0; + width: 13.9rem; + padding: 1.6rem 0; + border: 0.1rem solid var(--gray300); + border-radius: 0.8rem; + background-color: var(--white); + font-size: 1.6rem; + line-height: 1.9rem; + color: var(--gray500); + } } } .link-login { diff --git a/styles/scss/helpers/_common.scss b/styles/scss/helpers/_common.scss index 392b9177e..a0812a9eb 100644 --- a/styles/scss/helpers/_common.scss +++ b/styles/scss/helpers/_common.scss @@ -19,6 +19,7 @@ --gray600: #4b5563; --gray500: #6b7280; --gray400: #9ca3af; + --gray300: #d1d5db; --gray200: #e5e7eb; --gray100: #f3f4f6; --gray50: #f9fafb; From 5926f7eef7d4cd6396a04d6b3c0a47b710535b40 Mon Sep 17 00:00:00 2001 From: ksm Date: Tue, 3 Dec 2024 21:15:44 +0900 Subject: [PATCH 4/8] =?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=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/api/authApi.ts | 35 ++++++++++++++++++++++++++++++++--- pages/login.tsx | 4 ++-- pages/signup.tsx | 16 ++++++++++++---- styles/css/style.css | 6 ++++++ styles/scss/pages/_auth.scss | 6 ++++++ types/auth.ts | 2 +- 6 files changed, 59 insertions(+), 10 deletions(-) diff --git a/pages/api/authApi.ts b/pages/api/authApi.ts index 53496822b..63c80e6db 100644 --- a/pages/api/authApi.ts +++ b/pages/api/authApi.ts @@ -1,6 +1,6 @@ -import { LoginInterface } from "@/types/auth"; +import { LoginInterface, SignupInterface } from "@/types/auth"; -const LoginAndSetToken = async ({ email, password }: LoginInterface) => { +const loginAndSetToken = async ({ email, password }: LoginInterface) => { try { const response = await fetch( `${process.env.NEXT_PUBLIC_SERVER_URL}/auth/signIn`, @@ -33,4 +33,33 @@ const LoginAndSetToken = async ({ email, password }: LoginInterface) => { } }; -export { LoginAndSetToken }; +const signUp = async ({ email, nickname, password }: SignupInterface) => { + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_SERVER_URL}/auth/signUp`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email, + nickname, + password, + passwordConfirmation: password, + }), + } + ); + + if (!response.ok) { + console.log(response.statusText); + } + + return await response.json(); + } catch (error) { + console.error("초기 로그인 요청 실패:", error); + throw error; + } +}; + +export { loginAndSetToken, signUp }; diff --git a/pages/login.tsx b/pages/login.tsx index 04a0d8bd1..ec2759c9d 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -4,7 +4,7 @@ import PassWordInput from "@/components/auth/PassWordInput"; import Image from "next/image"; import Link from "next/link"; import { SubmitHandler, useForm } from "react-hook-form"; -import { LoginAndSetToken } from "./api/authApi"; +import { loginAndSetToken } from "./api/authApi"; import { LoginInterface } from "@/types/auth"; import { useRouter } from "next/router"; @@ -28,7 +28,7 @@ function Login() { email, password, }) => { - await LoginAndSetToken({ email, password }); + await loginAndSetToken({ email, password }); router.push("/"); }; diff --git a/pages/signup.tsx b/pages/signup.tsx index 5557d0a4e..6061ff843 100644 --- a/pages/signup.tsx +++ b/pages/signup.tsx @@ -7,6 +7,9 @@ import { SignupInterface } from "@/types/auth"; import Image from "next/image"; import Link from "next/link"; import { SubmitHandler, useForm } from "react-hook-form"; +import { signUp } from "./api/authApi"; +import { useState } from "react"; +import { useRouter } from "next/router"; const INITIAL_FORM_STATE: SignupInterface = { email: "", @@ -16,6 +19,7 @@ const INITIAL_FORM_STATE: SignupInterface = { }; function Signup() { + const router = useRouter(); const { register, handleSubmit, @@ -25,14 +29,17 @@ function Signup() { mode: "onBlur", defaultValues: INITIAL_FORM_STATE, }); + const [error, setError] = useState(""); - const onSubmit: SubmitHandler = ({ + const onSubmit: SubmitHandler = async ({ email, nickname, password, - passwordConfirm, }) => { - console.log(email, nickname, password, passwordConfirm); + const res = await signUp({ email, nickname, password }); + + if (res.message) setError(res.message); + else router.push("/login"); }; return ( @@ -56,7 +63,7 @@ function Signup() { register={register("email", { required: "이메일을 입력해주세요.", pattern: { - value: /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,}$/, + value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, message: "잘못된 이메일입니다.", }, })} @@ -91,6 +98,7 @@ function Signup() { errorMessage={errors.passwordConfirm?.message} /> + {error &&
{error}
}
); } diff --git a/styles/css/style.css b/styles/css/style.css index 688e40659..6ff5df027 100644 --- a/styles/css/style.css +++ b/styles/css/style.css @@ -2028,6 +2028,12 @@ footer .footer-wrap .sns a img { color: var(--primary100); text-decoration: underline; } +.container .res-message { + margin-top: 1.5rem; + font-size: 1.8rem; + font-weight: var(--bold); + color: var(--error); +} /* BreakPoint Mobile */ @media (max-width: 767px) { diff --git a/styles/scss/pages/_auth.scss b/styles/scss/pages/_auth.scss index 4292a552e..d5ba83973 100644 --- a/styles/scss/pages/_auth.scss +++ b/styles/scss/pages/_auth.scss @@ -143,6 +143,12 @@ } } } + .res-message { + margin-top: 1.5rem; + font-size: 1.8rem; + font-weight: var(--bold); + color: var(--error); + } } /* BreakPoint Mobile */ diff --git a/types/auth.ts b/types/auth.ts index fb1cac34f..1622cd505 100644 --- a/types/auth.ts +++ b/types/auth.ts @@ -7,7 +7,7 @@ interface SignupInterface { email: string; nickname: string; password: string; - passwordConfirm: string; + passwordConfirm?: string; } export type { LoginInterface, SignupInterface }; From 151d9ec72369ca353ff6aa936eb62f1a0dd04146 Mon Sep 17 00:00:00 2001 From: ksm Date: Tue, 3 Dec 2024 21:28:28 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[feat]:=20accessToken=20=EC=9E=88=EB=8A=94?= =?UTF-8?q?=20=EA=B2=BD=EC=9A=B0=20redirection=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/login.tsx | 17 ++++++++++++++++- pages/signup.tsx | 10 +++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pages/login.tsx b/pages/login.tsx index ec2759c9d..f51975dd3 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -7,6 +7,7 @@ import { SubmitHandler, useForm } from "react-hook-form"; import { loginAndSetToken } from "./api/authApi"; import { LoginInterface } from "@/types/auth"; import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; const INITIAL_FORM_STATE: LoginInterface = { email: "", @@ -23,6 +24,7 @@ function Login() { mode: "onBlur", defaultValues: INITIAL_FORM_STATE, }); + const [isLoading, setIsLoading] = useState(true); const onSubmit: SubmitHandler = async ({ email, @@ -32,6 +34,13 @@ function Login() { router.push("/"); }; + useEffect(() => { + if (localStorage.getItem("accessToken")) router.push("/"); + else setIsLoading(false); + }, [router]); + + if (isLoading) return null; + return (

@@ -50,7 +59,13 @@ function Login() { handleSubmit={handleSubmit(onSubmit)} > = async ({ @@ -42,6 +43,13 @@ function Signup() { else router.push("/login"); }; + useEffect(() => { + if (localStorage.getItem("accessToken")) router.push("/"); + else setIsLoading(false); + }, [router]); + + if (isLoading) return null; + return (

From a79afae687501b19d0a0be115854fda748460818 Mon Sep 17 00:00:00 2001 From: ksm Date: Thu, 5 Dec 2024 00:00:36 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[refactor]:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20isLogin=20property=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/common/Header.tsx | 28 ++++++++++++++++++---------- pages/addItem/index.tsx | 2 +- pages/addboard/index.tsx | 2 +- pages/boards/[id].tsx | 2 +- pages/boards/index.tsx | 2 +- pages/items/[id].tsx | 2 +- pages/items/index.tsx | 2 +- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/components/common/Header.tsx b/components/common/Header.tsx index 9f4c757fd..271da78c7 100644 --- a/components/common/Header.tsx +++ b/components/common/Header.tsx @@ -2,23 +2,31 @@ import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; import ProfileIcon from "../Icons/ProfileIcon"; -import { useState } from "react"; +import { useEffect, useState } from "react"; -const getToken = () => { - if (typeof window === "undefined") { - // 서버 환경 - return null; - } - // 브라우저 환경 - return localStorage.getItem("accessToken"); -}; +// const getToken = () => { +// if (typeof window === "undefined") { +// // 서버 환경 +// return null; +// } +// // 브라우저 환경 +// return localStorage.getItem("accessToken"); +// }; function Header() { const router = useRouter(); const { pathname } = router; - const hasToken = getToken(); + const [hasToken, setHasToken] = useState(false); const [isOpen, setIsOpen] = useState(false); + useEffect(() => { + const token = + typeof window !== "undefined" + ? localStorage.getItem("accessToken") + : null; + setHasToken(!!token); + }, []); + const handleLogout = () => { localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); diff --git a/pages/addItem/index.tsx b/pages/addItem/index.tsx index 29ded9eed..7c9b7a347 100644 --- a/pages/addItem/index.tsx +++ b/pages/addItem/index.tsx @@ -32,7 +32,7 @@ function AddItem() { }, [userInput]); return ( <> -
+
diff --git a/pages/addboard/index.tsx b/pages/addboard/index.tsx index c74530290..ea8b3ada3 100644 --- a/pages/addboard/index.tsx +++ b/pages/addboard/index.tsx @@ -29,7 +29,7 @@ function AddBoard() { }; return ( <> -
+
diff --git a/pages/boards/[id].tsx b/pages/boards/[id].tsx index 943f00f5b..e67d8acab 100644 --- a/pages/boards/[id].tsx +++ b/pages/boards/[id].tsx @@ -120,7 +120,7 @@ function Detail() { }, [handleObserver]); return ( <> -
+
diff --git a/pages/boards/index.tsx b/pages/boards/index.tsx index aa695470c..e8a0be55d 100644 --- a/pages/boards/index.tsx +++ b/pages/boards/index.tsx @@ -5,7 +5,7 @@ import Header from "@/components/common/Header"; function index() { return ( <> -
+
diff --git a/pages/items/[id].tsx b/pages/items/[id].tsx index ad9dc4135..e0434c3e9 100644 --- a/pages/items/[id].tsx +++ b/pages/items/[id].tsx @@ -145,7 +145,7 @@ function Detail() { }, [handleObserver]); return ( <> -
+
diff --git a/pages/items/index.tsx b/pages/items/index.tsx index 348680a49..825329bd2 100644 --- a/pages/items/index.tsx +++ b/pages/items/index.tsx @@ -5,7 +5,7 @@ import ProductList from "@/components/items/ProductList"; function Items() { return ( <> -
+
From 1898c76ee68b9e7024214a382ef14b3b1b84c2d0 Mon Sep 17 00:00:00 2001 From: ksm Date: Thu, 5 Dec 2024 00:01:08 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[chore]:=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=9A=A9=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/next.config.js b/next.config.js index 141701109..3f61e2ceb 100644 --- a/next.config.js +++ b/next.config.js @@ -13,6 +13,16 @@ const nextConfig = { hostname: "**.com", pathname: "/**", }, + { + protocol: "https", + hostname: "**.net", + pathname: "/**", + }, + { + protocol: "https", + hostname: "**.co", + pathname: "/**", + }, { protocol: "http", hostname: "**.co.kr", @@ -23,6 +33,16 @@ const nextConfig = { hostname: "**.com", pathname: "/**", }, + { + protocol: "http", + hostname: "**.net", + pathname: "/**", + }, + { + protocol: "http", + hostname: "**.co", + pathname: "/**", + }, { protocol: "https", hostname: "example.com", From 8f964844d4271eb9b1f6e7801ce233acdb484b87 Mon Sep 17 00:00:00 2001 From: ksm Date: Sat, 7 Dec 2024 20:33:47 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[refactor]:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/common/Header.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/components/common/Header.tsx b/components/common/Header.tsx index 271da78c7..10c50b312 100644 --- a/components/common/Header.tsx +++ b/components/common/Header.tsx @@ -4,15 +4,6 @@ import { useRouter } from "next/router"; import ProfileIcon from "../Icons/ProfileIcon"; import { useEffect, useState } from "react"; -// const getToken = () => { -// if (typeof window === "undefined") { -// // 서버 환경 -// return null; -// } -// // 브라우저 환경 -// return localStorage.getItem("accessToken"); -// }; - function Header() { const router = useRouter(); const { pathname } = router; @@ -30,6 +21,7 @@ function Header() { const handleLogout = () => { localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); + setHasToken(false); }; return (