From 4715e1697518f02de95f7e8ecbf62b08b9bec40b Mon Sep 17 00:00:00 2001 From: hyunseo11 <70734028+hyunseo11@users.noreply.github.com> Date: Sat, 18 May 2024 13:57:33 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- weekly-mission/next-project/db/dbConnect.ts | 19 ++ weekly-mission/next-project/global.d.ts | 10 + weekly-mission/next-project/package-lock.json | 191 +++++++++++++++++- weekly-mission/next-project/package.json | 1 + .../next-project/pages/api/check-email.tsx | 33 +++ .../next-project/pages/api/sign-in.tsx | 17 +- .../next-project/pages/api/sign-up.tsx | 14 ++ weekly-mission/next-project/pages/signin.tsx | 11 +- weekly-mission/next-project/pages/signup.tsx | 54 ++++- .../src/components/Form/index.tsx | 36 +++- .../src/components/Input/index.tsx | 14 +- 11 files changed, 377 insertions(+), 23 deletions(-) create mode 100644 weekly-mission/next-project/db/dbConnect.ts create mode 100644 weekly-mission/next-project/pages/api/check-email.tsx create mode 100644 weekly-mission/next-project/pages/api/sign-up.tsx diff --git a/weekly-mission/next-project/db/dbConnect.ts b/weekly-mission/next-project/db/dbConnect.ts new file mode 100644 index 000000000..dfc4c0b6d --- /dev/null +++ b/weekly-mission/next-project/db/dbConnect.ts @@ -0,0 +1,19 @@ +import { MongoClient } from "mongodb"; + +const url = + "mongodb+srv://hs0727:hsjh1004!@cluster0.aeptwaq.mongodb.net/check-email?retryWrites=true&w=majority&appName=Cluster0"; +const options: any = { useNewUrlParser: true }; + +let cachedClient: MongoClient | null = null; + +export async function connectDB(): Promise { + if (cachedClient) { + return cachedClient; + } + + const client = new MongoClient(url, options); + await client.connect(); + + cachedClient = client; + return client; +} diff --git a/weekly-mission/next-project/global.d.ts b/weekly-mission/next-project/global.d.ts index 1aa65443c..b91727dc0 100644 --- a/weekly-mission/next-project/global.d.ts +++ b/weekly-mission/next-project/global.d.ts @@ -3,6 +3,16 @@ declare module "*.module.css" { export default classes; } +// global.d.ts + +import type { MongoClient } from "mongodb"; + +declare global { + namespace globalThis { + var _mongo: Promise; + } +} + declare module "*.png"; declare module "*.jpg"; declare module "*.svg"; diff --git a/weekly-mission/next-project/package-lock.json b/weekly-mission/next-project/package-lock.json index d7a832f8c..bafa63cf3 100644 --- a/weekly-mission/next-project/package-lock.json +++ b/weekly-mission/next-project/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "axios": "^1.6.8", + "mongoose": "^8.4.0", "next": "14.2.3", "react": "^18", "react-dom": "^18", @@ -167,6 +168,14 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz", + "integrity": "sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@next/env": { "version": "14.2.3", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", @@ -412,6 +421,19 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@typescript-eslint/parser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", @@ -871,6 +893,14 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.7.0.tgz", + "integrity": "sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -1066,7 +1096,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2766,6 +2795,14 @@ "node": ">=4.0" } }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2847,6 +2884,11 @@ "node": "14 || >=16.14" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2918,11 +2960,109 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mongodb": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.6.2.tgz", + "integrity": "sha512-ZF9Ugo2JCG/GfR7DEb4ypfyJJyiKbg5qBYKRintebj8+DNS33CyGMkWbrS9lara+u+h+yEOGSRiLhFO/g1s1aw==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.4.0.tgz", + "integrity": "sha512-fgqRMwVEP1qgRYfh+tUe2YBBFnPO35FIg2lfFH+w9IhRGg1/ataWGIqvf/MjwM29cZ60D5vSnqtN2b8Qp0sOZA==", + "dependencies": { + "bson": "^6.7.0", + "kareem": "2.6.3", + "mongodb": "6.6.2", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -3329,7 +3469,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "engines": { "node": ">=6" } @@ -3672,6 +3811,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -3701,6 +3845,14 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -3968,6 +4120,17 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -4137,6 +4300,26 @@ "punycode": "^2.1.0" } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/weekly-mission/next-project/package.json b/weekly-mission/next-project/package.json index 87dc02d9f..0ec4f7eb7 100644 --- a/weekly-mission/next-project/package.json +++ b/weekly-mission/next-project/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "axios": "^1.6.8", + "mongoose": "^8.4.0", "next": "14.2.3", "react": "^18", "react-dom": "^18", diff --git a/weekly-mission/next-project/pages/api/check-email.tsx b/weekly-mission/next-project/pages/api/check-email.tsx new file mode 100644 index 000000000..7db8c99f1 --- /dev/null +++ b/weekly-mission/next-project/pages/api/check-email.tsx @@ -0,0 +1,33 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { connectDB } from "db/dbConnect"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + try { + // 데이터베이스 연결 + const client = await connectDB(); + const db = client.db(); + + if (req.method !== "POST") { + return res.status(405).json({ message: "Method not allowed" }); + } + + const { email } = req.body; + + // 이메일이 이미 존재하는지 확인 + const existingEmail = await db.collection("users").findOne({ email }); + if (existingEmail) { + return res.status(409).json({ message: "이미 사용 중인 이메일입니다." }); + } + + // 이메일이 존재하지 않으면 새로 추가 + await db.collection("users").insertOne({ email }); + + return res.status(200).json({ message: "사용 가능한 이메일입니다." }); + } catch (error) { + console.error("Error:", error); + return res.status(500).json({ message: "서버 오류 발생" }); + } +} diff --git a/weekly-mission/next-project/pages/api/sign-in.tsx b/weekly-mission/next-project/pages/api/sign-in.tsx index df1122f96..26eda475d 100644 --- a/weekly-mission/next-project/pages/api/sign-in.tsx +++ b/weekly-mission/next-project/pages/api/sign-in.tsx @@ -1,16 +1,11 @@ import { NextApiRequest, NextApiResponse } from "next"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method === "POST") { - const { email, password } = req.body; +export default function handler(req: NextApiRequest, res: NextApiResponse) { + const { email, password } = req.body; - if (email === "test@codeit.com" && password === "sprint101") { - res.status(200).json({ success: true }); - } else { - res.status(401).json({ success: false, message: "로그인 실패" }); - } + if (email === "test@codeit.com" && password === "sprint101") { + res.status(200).json({ success: true }); + } else { + res.status(401).json({ success: false, message: "로그인 오류" }); } } diff --git a/weekly-mission/next-project/pages/api/sign-up.tsx b/weekly-mission/next-project/pages/api/sign-up.tsx new file mode 100644 index 000000000..6bc1715e1 --- /dev/null +++ b/weekly-mission/next-project/pages/api/sign-up.tsx @@ -0,0 +1,14 @@ +import { NextApiRequest, NextApiResponse } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === "POST") { + const { email, password } = req.body; + + res.status(200).json({ success: true }); + } else { + res.status(405).json({ message: "Method Not Allowed" }); + } +} diff --git a/weekly-mission/next-project/pages/signin.tsx b/weekly-mission/next-project/pages/signin.tsx index b456c5a18..49fb6d658 100644 --- a/weekly-mission/next-project/pages/signin.tsx +++ b/weekly-mission/next-project/pages/signin.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { useRouter } from "next/router"; import instance from "lib/api"; import styles from "@/styles/sign.module.css"; @@ -10,6 +10,13 @@ function SigninPage() { {} ); + useEffect(() => { + const accessToken = localStorage.getItem("accessToken"); + if (accessToken) { + router.push("/folder"); + } + }, []); + const handleSignIn = async (data: any) => { const { email, password } = data; @@ -17,6 +24,8 @@ function SigninPage() { const response = await instance.post(`/sign-in`, { email, password }); if (response.status === 200) { // 로그인 성공 + const { accessToken } = response.data; + localStorage.setItem("accessToken", accessToken); router.push("/folder"); } else { // 로그인 실패 diff --git a/weekly-mission/next-project/pages/signup.tsx b/weekly-mission/next-project/pages/signup.tsx index f247ae9cc..0696d50e9 100644 --- a/weekly-mission/next-project/pages/signup.tsx +++ b/weekly-mission/next-project/pages/signup.tsx @@ -1,25 +1,75 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import { useRouter } from "next/router"; +import instance from "lib/api"; import styles from "@/styles/sign.module.css"; import Form from "@components/Form"; function SignupPage() { const router = useRouter(); + const [errors, setErrors] = useState<{ email?: string; password?: string }>( + {} + ); + + useEffect(() => { + const accessToken = localStorage.getItem("accessToken"); + if (accessToken) { + router.push("/folder"); + } + }, []); + + const checkEmailDuplication = async (email: string) => { + try { + // 이메일 중복 확인 요청 + const response = await instance.post(`/check-email`, { email }); + if (response.status === 200) { + // 중복되지 않은 경우 + setErrors((prevErrors) => ({ ...prevErrors, email: undefined })); + } + } catch (error) { + // 중복된 경우 + setErrors((prevErrors) => ({ + ...prevErrors, + email: "이미 사용 중인 이메일입니다.", + })); + } + }; - const handleSignup = async (data: any) => { + const handleSignup = async (data: { email: string; password: string }) => { const { email, password } = data; + try { + const response = await instance.post(`/sign-up`, { + email, + password, + }); + if (response.status === 200) { + //회원가입 성공 + const { accessToken } = response.data; + localStorage.setItem("accessToken", accessToken); + router.push("/folder"); + } else { + //회원가입 실패 + console.error("Sign up failed"); + } + } catch (error) { + setErrors({ + email: "이미 사용중인 이메일입니다.", + password: "비밀번호 오류입니다.", + }); + } }; return (
); diff --git a/weekly-mission/next-project/src/components/Form/index.tsx b/weekly-mission/next-project/src/components/Form/index.tsx index be826d52d..1245497a8 100644 --- a/weekly-mission/next-project/src/components/Form/index.tsx +++ b/weekly-mission/next-project/src/components/Form/index.tsx @@ -16,6 +16,7 @@ interface FormValues { interface FormProps { onSubmit: (data: FormValues) => void; + checkEmailDuplication?: (email: string) => Promise; headermessage: string; headerlink: string; buttonText: string; @@ -27,6 +28,7 @@ interface FormProps { const Form = ({ onSubmit, + checkEmailDuplication, headermessage, headerlink, buttonText, @@ -42,6 +44,7 @@ const Form = ({ setError, clearErrors, formState: { errors }, + trigger, } = useForm({ mode: "onBlur" }); const password = watch("password"); @@ -65,6 +68,29 @@ const Form = ({ clearErrors(fieldName); }; + const handleEmailBlur = async (event: React.FocusEvent) => { + const { value } = event.target; + // 이메일 필드의 유효성 검사를 수행하고, 검사 결과를 확인 + const isValid = await trigger("email"); + + // 이메일이 유효하지 않은 경우, 중복 검사를 수행하지 않고 함수 종료 + if (!isValid) { + return; + } + + if (checkEmailDuplication) { + // 중복 검사를 수행 + await checkEmailDuplication(value); + } + }; + + const handlePasswordBlur = async ( + event: React.FocusEvent + ) => { + // 비밀번호 필드의 유효성 검사를 수행하고, 검사 결과를 확인 + await trigger("password"); + }; + return (
@@ -93,6 +119,7 @@ const Form = ({ })} error={errors.email?.message || errorMessage} onFocus={() => onFocusIn("email")} + onBlur={handleEmailBlur} /> onFocusIn("password")} + onBlur={handlePasswordBlur} /> {isPasswordConfirmation && ( @@ -139,17 +167,17 @@ const Form = ({

{socialProvidersText}

- - +
diff --git a/weekly-mission/next-project/src/components/Input/index.tsx b/weekly-mission/next-project/src/components/Input/index.tsx index 415501a0e..2c7df4b47 100644 --- a/weekly-mission/next-project/src/components/Input/index.tsx +++ b/weekly-mission/next-project/src/components/Input/index.tsx @@ -9,6 +9,7 @@ interface InputFieldProps { register: UseFormRegisterReturn; error?: string | { email?: string; password?: string }; onFocus?: () => void; + onBlur?: (event: React.FocusEvent) => void; } const InputField: React.FC = ({ @@ -18,6 +19,7 @@ const InputField: React.FC = ({ register, error, onFocus, + onBlur, }: InputFieldProps) => { const renderErrorMessage = () => { if (!error) return null; @@ -38,6 +40,12 @@ const InputField: React.FC = ({ } }; + const handleBlur = async (event: React.FocusEvent) => { + if (onBlur) { + await onBlur(event); // 외부에서 전달된 onBlur 이벤트 핸들러 실행 + } + }; + return (
= ({ {...register} className={styles.input_form} onFocus={onFocus} + onBlur={handleBlur} />
{renderErrorMessage()}