From 34cee1d1dcbefb2aaedf70c2e51cec7e7f8acf2d Mon Sep 17 00:00:00 2001 From: LEE-DA-EUN Date: Tue, 11 Mar 2025 22:52:28 +0900 Subject: [PATCH 1/9] =?UTF-8?q?#26=20feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=83=80=EC=9D=B4=ED=8B=80?= =?UTF-8?q?=EB=B0=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/login/page.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index 010a703..b4548d1 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; +import Title from "@/components/Title"; const LoginPage = () => { const [id, setId] = useState(""); const [password, setPassword] = useState(""); @@ -17,10 +18,8 @@ const LoginPage = () => { }; return ( -
-
- 상단바 공간 -
+
+ <div className="w-full mt-8"> <label className="block text-left text-lg font-medium text-gray-700"> From 1c6b5477c990384e5309dd5c94f9de5d4fc50687 Mon Sep 17 00:00:00 2001 From: LEE-DA-EUN <delee3064@gmail.com> Date: Tue, 11 Mar 2025 22:56:08 +0900 Subject: [PATCH 2/9] =?UTF-8?q?#26=20feat:=20=EC=95=84=EC=9D=B4=EB=94=94/?= =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=B0=BE=EA=B8=B0=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=83=80=EC=9D=B4=ED=8B=80?= =?UTF-8?q?=EB=B0=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/find/page.tsx | 7 +++---- frontend/src/app/login/page.tsx | 2 +- frontend/src/components/Title.tsx | 16 +++++++++------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/find/page.tsx b/frontend/src/app/find/page.tsx index f050db8..94fb430 100644 --- a/frontend/src/app/find/page.tsx +++ b/frontend/src/app/find/page.tsx @@ -5,6 +5,7 @@ import { useState, useEffect } from "react"; import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid"; import PrimaryButton from "@/components/find/PrimaryButton"; import InputField from "@/components/find/InputField"; +import Title from "@/components/Title"; const FindPage = () => { const router = useRouter(); @@ -43,10 +44,8 @@ const FindPage = () => { }; return ( - <div className="flex flex-col items-center w-[390px] h-[844px] mx-auto p-6"> - <div className="w-full h-16 border-b border-gray-400 flex items-center justify-center text-gray-500"> - 상단바 공간 - </div> + <div className="flex flex-col items-center w-[390px] pt-[164px] h-[844px] mx-auto p-6"> + <Title pageTitle="아이디 / 비밀번호 찾기" /> <div className="w-full flex border-b"> <button diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index b4548d1..32c0daa 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -19,7 +19,7 @@ const LoginPage = () => { return ( <div className="flex flex-col items-center w-[390px] pt-[164px] h-[844px] mx-auto p-6"> - <Title pageTitle="회원가입" /> + <Title pageTitle="로그인" /> <div className="w-full mt-8"> <label className="block text-left text-lg font-medium text-gray-700"> diff --git a/frontend/src/components/Title.tsx b/frontend/src/components/Title.tsx index 8a0d54d..10593bf 100644 --- a/frontend/src/components/Title.tsx +++ b/frontend/src/components/Title.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import Image from 'next/image'; +import React from "react"; +import Image from "next/image"; interface TitleProps { - pageTitle: string; // 페이지 이름을 props로 받습니다 + pageTitle: string; // 페이지 이름을 props로 받습니다 } const Title = ({ pageTitle }: TitleProps) => { @@ -18,15 +18,17 @@ const Title = ({ pageTitle }: TitleProps) => { className="object-contain" /> </div> - + {/* Page title */} - <h1 className="absolute left-1/2 top-[24px] -translate-x-1/2 + <h1 + className="absolute left-1/2 top-[24px] -translate-x-1/2 font-inter font-semibold text-[20px] leading-[24px] - tracking-[-0.025em] text-black w-[95px] text-center"> + tracking-[-0.025em] text-black w-auto text-center" + > {pageTitle} </h1> </div> ); }; -export default Title; \ No newline at end of file +export default Title; From 45465989fe6c1fbbd6495b4547e4782e51fd065d Mon Sep 17 00:00:00 2001 From: LEE-DA-EUN <delee3064@gmail.com> Date: Mon, 31 Mar 2025 23:07:49 +0900 Subject: [PATCH 3/9] =?UTF-8?q?#26=20feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=EC=8B=9C=EC=97=90=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EB=94=94=EA=B0=80=20=EB=B3=B4=EC=9D=B4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # frontend/src/app/find/page.tsx --- frontend/src/app/find/page.tsx | 14 +++++++++++++- frontend/src/components/find/InputField.tsx | 10 +++++++++- frontend/src/types/components.ts | 1 + 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/find/page.tsx b/frontend/src/app/find/page.tsx index 94fb430..4741810 100644 --- a/frontend/src/app/find/page.tsx +++ b/frontend/src/app/find/page.tsx @@ -43,6 +43,7 @@ const FindPage = () => { setShowPasswordReset(false); }; + return ( <div className="flex flex-col items-center w-[390px] pt-[164px] h-[844px] mx-auto p-6"> <Title pageTitle="아이디 / 비밀번호 찾기" /> @@ -91,6 +92,13 @@ const FindPage = () => { ) ) : showPasswordReset ? ( <div> + <InputField + label="아이디" + type="text" + placeholder="아이디 입력" + disabled + /> + <InputField label="변경 비밀번호" type="password" @@ -110,7 +118,11 @@ const FindPage = () => { </div> ) : ( <div> - <InputField label="아이디" type="text" placeholder="아이디 입력" /> + <InputField + label="아이디" + type="text" + placeholder="아이디 입력" + /> <div className="w-full flex items-center"> <InputField label="이메일" type="email" placeholder="이메일 주소 입력"> <button className="font-bold px-4 py-2 bg-primary text-white rounded-md text-sm whitespace-nowrap" onClick={() => setShowVerification(true)}> diff --git a/frontend/src/components/find/InputField.tsx b/frontend/src/components/find/InputField.tsx index 1918463..021f9c7 100644 --- a/frontend/src/components/find/InputField.tsx +++ b/frontend/src/components/find/InputField.tsx @@ -10,6 +10,7 @@ const InputField = ({ onChange, children, helperText, + disabled = false, }: InputFieldProps) => ( <div className="w-full mt-4"> <div className="flex items-center space-x-2"> @@ -26,7 +27,14 @@ const InputField = ({ placeholder={placeholder} value={value} onChange={onChange} - className="flex-grow border-b border-gray-600 outline-none py-2 text-lg mt-2" + disabled={disabled} + className={`flex-grow border-b outline-none py-2 text-lg mt-2 + ${ + disabled + ? "bg-gray-100 text-gray-500 cursor-not-allowed" + : "border-gray-600" + } + `} /> {children && <div className="ml-2">{children}</div>} </div> diff --git a/frontend/src/types/components.ts b/frontend/src/types/components.ts index 2838765..b06e683 100644 --- a/frontend/src/types/components.ts +++ b/frontend/src/types/components.ts @@ -6,6 +6,7 @@ export interface InputFieldProps { onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void; children?: React.ReactNode; helperText?: string; + disabled?: boolean; } export interface PrimaryButtonProps { From 11f9bdab537e6af62b3bab3bc1fc6685a6c27a1d Mon Sep 17 00:00:00 2001 From: LEE-DA-EUN <delee3064@gmail.com> Date: Wed, 2 Apr 2025 15:45:00 +0900 Subject: [PATCH 4/9] =?UTF-8?q?#26=20feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 115 +++++++++++++++++++++++++++---- frontend/package.json | 1 + frontend/src/app/signup/page.tsx | 88 ++++++++++++++++++----- frontend/src/utils/axios.ts | 10 +++ 4 files changed, 182 insertions(+), 32 deletions(-) create mode 100644 frontend/src/utils/axios.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 21891da..4a889ea 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@heroicons/react": "^2.2.0", + "axios": "^1.8.4", "next": "^15.2.3", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -1501,6 +1502,12 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -1565,6 +1572,17 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -1686,7 +1704,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1859,6 +1876,18 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2033,6 +2062,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2074,7 +2112,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2190,7 +2227,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2200,7 +2236,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2238,7 +2273,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2251,7 +2285,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2868,6 +2901,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -2901,6 +2954,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -2934,7 +3002,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2975,7 +3042,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3000,7 +3066,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3135,7 +3200,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3214,7 +3278,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3227,7 +3290,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -3243,7 +3305,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3961,7 +4022,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3991,6 +4051,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4662,6 +4743,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 76b6c3c..1c21dcd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@heroicons/react": "^2.2.0", + "axios": "^1.8.4", "next": "^15.2.3", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/frontend/src/app/signup/page.tsx b/frontend/src/app/signup/page.tsx index 30f3c8d..fab680e 100644 --- a/frontend/src/app/signup/page.tsx +++ b/frontend/src/app/signup/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import axios from "@/utils/axios"; import SignupInputField from "@/components/signup/SignupInputField"; import PrivacyAgreement from "@/components/signup/PrivacyAgreement"; import PrimaryButton from "@/components/find/PrimaryButton"; @@ -29,10 +30,15 @@ const Signup = () => { >(null); const [isPrivacyChecked, setIsPrivacyChecked] = useState(false); - const handleCheckUsername = () => { - // 아이디 중복 확인 -> 추후 서버 연동 - // 예제: "testuser"는 이미 사용 중 - setIsUsernameAvailable(username !== "testuser"); + const handleCheckUsername = async () => { + try { + const res = await axios.post("/api/v1/auth/id-check", { username }); + console.log("중복 확인 응답:", res.data); + setIsUsernameAvailable(res.data.status === 200); + } catch (error) { + console.error("Username check failed", error); + setIsUsernameAvailable(false); + } }; const validatePassword = (value: string) => { @@ -47,16 +53,59 @@ const Signup = () => { setIsPasswordMatch(password === value); }; - const handleSendVerification = () => { - // 이메일 인증 요청 -> 추후 서버 연동 - setShowVerificationField(true); - // 예제: "test@example.com"은 이미 사용 중 - setIsEmailAvailable(email !== "test@example.com"); + const handleSendVerification = async () => { + try { + const res = await axios.post("/api/v1/email/email-certification", { + username, + email, + }); + if (res.data.status === 200) { + setShowVerificationField(true); + setIsEmailAvailable(true); + } else { + setIsEmailAvailable(false); + alert(res.data.message || "이메일 인증 요청 실패"); + } + } catch (error) { + console.error("Email verification request failed", error); + setIsEmailAvailable(false); + } }; - const handleVerifyCode = () => { - // 인증번호 확인 -> 추후 서버 연동 - setIsVerificationSuccess(verificationCode === "123456"); + const handleVerifyCode = async () => { + try { + const res = await axios.post("/api/v1/auth/check-certification", { + username, + email, + certificationNumber: verificationCode, + }); + setIsVerificationSuccess(res.data.status === 200); + } catch (error) { + console.error("Verification failed", error); + setIsVerificationSuccess(false); + } + }; + + const handleSignup = async () => { + try { + const res = await axios.post("/api/v1/auth/sign-up", { + nickname: name, + username, + password, + confirmPassword, + email, + phone, + certificationNumber: verificationCode, + }); + if (res.data.status === 200) { + alert("회원가입 완료"); + } else { + alert(res.data.message || "회원가입 실패"); + } + } catch (error) { + console.error("Signup error", error); + alert("회원가입 중 오류가 발생했습니다"); + } }; const isSignupDisabled = @@ -67,10 +116,10 @@ const Signup = () => { !name || !phone || !isPrivacyChecked || - isUsernameAvailable === false || - isEmailAvailable === false || + isUsernameAvailable !== true || !isPasswordValid || - !isPasswordMatch; + !isPasswordMatch || + !isVerificationSuccess; return ( <div className="max-w-md mx-auto p-6 pt-[164px]"> @@ -81,7 +130,10 @@ const Signup = () => { type="text" placeholder="아이디 입력" value={username} - onChange={(e) => setUsername(e.target.value)} + onChange={(e) => { + setUsername(e.target.value); + setIsUsernameAvailable(null); + }} buttonText="중복 확인" onButtonClick={handleCheckUsername} success={isUsernameAvailable === true} @@ -120,9 +172,9 @@ const Signup = () => { onChange={(e) => setEmail(e.target.value)} buttonText="인증" onButtonClick={handleSendVerification} - success={isEmailAvailable === true} error={isEmailAvailable === false} errorText="* 이미 사용 중인 이메일입니다." + success={isEmailAvailable === true} /> {showVerificationField && ( @@ -163,7 +215,7 @@ const Signup = () => { <PrimaryButton text="회원가입" - onClick={() => alert("회원가입 완료")} + onClick={handleSignup} disabled={isSignupDisabled} /> </div> diff --git a/frontend/src/utils/axios.ts b/frontend/src/utils/axios.ts new file mode 100644 index 0000000..da752f9 --- /dev/null +++ b/frontend/src/utils/axios.ts @@ -0,0 +1,10 @@ +import axios from "axios"; + +const instance = axios.create({ + baseURL: "http://localhost:8080", // 백엔드 서버 주소 + headers: { + "Content-Type": "application/json", + }, +}); + +export default instance; From 8a41b53c4eb54f38d25ddde986efdd0756875245 Mon Sep 17 00:00:00 2001 From: LEE-DA-EUN <delee3064@gmail.com> Date: Fri, 4 Apr 2025 17:26:08 +0900 Subject: [PATCH 5/9] =?UTF-8?q?#26=20feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/login/page.tsx | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index 32c0daa..a18e3fa 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; +import axios from "@/utils/axios"; import Title from "@/components/Title"; const LoginPage = () => { const [id, setId] = useState(""); @@ -8,13 +9,28 @@ const LoginPage = () => { const [error, setError] = useState(false); const router = useRouter(); - const handleLogin = () => { + const handleLogin = async () => { if (!id || !password) { setError(true); return; } - setError(false); - console.log("로그인 요청"); // 추후 서버 연동 + try { + const res = await axios.post("/api/v1/auth/sign-in", { + username: id, + password, + }); + if (res.data.status === 200) { + const { accessToken, role } = res.data.data; + localStorage.setItem("accessToken", accessToken); + localStorage.setItem("userRole", role); + router.push("/"); + } else { + setError(true); + } + } catch (err) { + console.error("Login failed:", err); + setError(true); + } }; return ( @@ -65,7 +81,9 @@ const LoginPage = () => { </button> <div className="w-full flex justify-between mt-4 text-xs text-gray-700"> - <button className="underline">회원가입</button> + <button className="underline" onClick={() => router.push("/signup")}> + 회원가입 + </button> <div className="flex gap-4"> <button className="underline" From 6e3e500b5933f74840a25b72df45ec931f1e3b6e Mon Sep 17 00:00:00 2001 From: LEE-DA-EUN <delee3064@gmail.com> Date: Fri, 4 Apr 2025 17:58:43 +0900 Subject: [PATCH 6/9] =?UTF-8?q?#26=20design:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=84=B1=EA=B3=B5=EC=8B=9C=20=EB=AA=A8=EB=8B=AC=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 --- frontend/package-lock.json | 62 +++++++++++++++++++++++++++++++++ frontend/package.json | 3 ++ frontend/src/app/login/page.tsx | 43 +++++++++++++++++++++-- 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4a889ea..a4d1b30 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,12 +10,15 @@ "dependencies": { "@heroicons/react": "^2.2.0", "axios": "^1.8.4", + "canvas-confetti": "^1.9.3", + "framer-motion": "^12.6.3", "next": "^15.2.3", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@eslint/eslintrc": "^3", + "@types/canvas-confetti": "^1.9.0", "@types/node": "^20.17.24", "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", @@ -927,6 +930,13 @@ "tslib": "^2.8.0" } }, + "node_modules/@types/canvas-confetti": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.9.0.tgz", + "integrity": "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1770,6 +1780,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas-confetti": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.3.tgz", + "integrity": "sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==", + "license": "ISC", + "funding": { + "type": "donate", + "url": "https://www.paypal.me/kirilvatev" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2983,6 +3003,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.6.3.tgz", + "integrity": "sha512-2hsqknz23aloK85bzMc9nSR2/JP+fValQ459ZTVElFQ0xgwR2YqNjYSuDZdFBPOwVCt4Q9jgyTt6hg6sVOALzw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.6.3", + "motion-utils": "^12.6.3", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4105,6 +4152,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.6.3.tgz", + "integrity": "sha512-gRY08RjcnzgFYLemUZ1lo/e9RkBxR+6d4BRvoeZDSeArG4XQXERSPapKl3LNQRu22Sndjf1h+iavgY0O4NrYqA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.6.3" + } + }, + "node_modules/motion-utils": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.6.3.tgz", + "integrity": "sha512-R/b3Ia2VxtTNZ4LTEO5pKYau1OUNHOuUfxuP0WFCTDYdHkeTBR9UtxR1cc8mDmKr8PEhmmfnTKGz3rSMjNRoRg==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1c21dcd..6d156bc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,12 +11,15 @@ "dependencies": { "@heroicons/react": "^2.2.0", "axios": "^1.8.4", + "canvas-confetti": "^1.9.3", + "framer-motion": "^12.6.3", "next": "^15.2.3", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@eslint/eslintrc": "^3", + "@types/canvas-confetti": "^1.9.0", "@types/node": "^20.17.24", "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index a18e3fa..c8d7776 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -3,12 +3,25 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; import axios from "@/utils/axios"; import Title from "@/components/Title"; +import { motion, AnimatePresence } from "framer-motion"; +import confetti from "canvas-confetti"; const LoginPage = () => { const [id, setId] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(false); + const [showModal, setShowModal] = useState(false); const router = useRouter(); + const fireConfetti = () => { + confetti({ + particleCount: 200, + spread: 120, + origin: { y: 0.6 }, + scalar: 0.8, + colors: ["#A3C4F3", "#FFB6B9", "#FFDAC1", "#E2F0CB", "#B5EAD7"], + }); + }; + const handleLogin = async () => { if (!id || !password) { setError(true); @@ -23,7 +36,12 @@ const LoginPage = () => { const { accessToken, role } = res.data.data; localStorage.setItem("accessToken", accessToken); localStorage.setItem("userRole", role); - router.push("/"); + setShowModal(true); + fireConfetti(); + setTimeout(() => { + setShowModal(false); + router.push("/"); + }, 2000); } else { setError(true); } @@ -34,7 +52,7 @@ const LoginPage = () => { }; return ( - <div className="flex flex-col items-center w-[390px] pt-[164px] h-[844px] mx-auto p-6"> + <div className="flex flex-col items-center w-[390px] pt-[164px] h-[844px] mx-auto p-6 relative overflow-hidden"> <Title pageTitle="로그인" /> <div className="w-full mt-8"> @@ -99,6 +117,27 @@ const LoginPage = () => { </button> </div> </div> + + <AnimatePresence> + {showModal && ( + <motion.div + className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50" + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + exit={{ opacity: 0 }} + > + <motion.div + className="bg-white p-6 rounded-lg shadow-lg text-center" + initial={{ scale: 0.8, opacity: 0 }} + animate={{ scale: 1, opacity: 1 }} + exit={{ scale: 0.8, opacity: 0 }} + transition={{ duration: 0.3 }} + > + <p className="text-lg font-semibold">로그인에 성공했습니다!</p> + </motion.div> + </motion.div> + )} + </AnimatePresence> </div> ); }; From a01909272db731e1d28bd0fdbf3ff1ce32bf6321 Mon Sep 17 00:00:00 2001 From: LEE-DA-EUN <delee3064@gmail.com> Date: Fri, 4 Apr 2025 18:01:25 +0900 Subject: [PATCH 7/9] =?UTF-8?q?#26=20feat:=20axios=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=EC=85=89=ED=84=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/axios.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frontend/src/utils/axios.ts b/frontend/src/utils/axios.ts index da752f9..658cbc5 100644 --- a/frontend/src/utils/axios.ts +++ b/frontend/src/utils/axios.ts @@ -7,4 +7,18 @@ const instance = axios.create({ }, }); +// accessToken 인터셉터 +instance.interceptors.request.use( + (config) => { + if (typeof window !== "undefined") { + const token = localStorage.getItem("accessToken"); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + } + return config; + }, + (error) => Promise.reject(error) +); + export default instance; From 027b36af03524d7d5ce08d833773e6cf4b2e13cc Mon Sep 17 00:00:00 2001 From: LEE-DA-EUN <delee3064@gmail.com> Date: Mon, 7 Apr 2025 15:45:32 +0900 Subject: [PATCH 8/9] =?UTF-8?q?#26=20feat:=20=EC=95=84=EC=9D=B4=EB=94=94?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/find/page.tsx | 77 ++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/frontend/src/app/find/page.tsx b/frontend/src/app/find/page.tsx index 4741810..d2ba50c 100644 --- a/frontend/src/app/find/page.tsx +++ b/frontend/src/app/find/page.tsx @@ -6,12 +6,12 @@ import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid"; import PrimaryButton from "@/components/find/PrimaryButton"; import InputField from "@/components/find/InputField"; import Title from "@/components/Title"; +import axios from "@/utils/axios"; const FindPage = () => { const router = useRouter(); - - // ✅ URL에서 직접 `tab`을 가져오지 않고, useState로 관리 const [tab, setTab] = useState<"id" | "password">("id"); + const [email, setEmail] = useState(""); const [foundId, setFoundId] = useState<string | null>(null); const [showVerification, setShowVerification] = useState(false); const [isVerified, setIsVerified] = useState(false); @@ -21,7 +21,6 @@ const FindPage = () => { const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); - // ✅ useEffect로 URL 쿼리 파라미터를 `tab` 상태로 반영 useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const currentTab = urlParams.get("tab"); @@ -43,6 +42,19 @@ const FindPage = () => { setShowPasswordReset(false); }; + const handleFindId = async () => { + try { + const res = await axios.post("/api/v1/member/find-userId", { email }); + if (res.data.status === 200) { + setFoundId(res.data.data); + } else { + alert(res.data.message || "아이디를 찾을 수 없습니다."); + } + } catch (err) { + console.error("아이디 찾기 실패", err); + alert("아이디 찾기 중 오류가 발생했습니다."); + } + }; return ( <div className="flex flex-col items-center w-[390px] pt-[164px] h-[844px] mx-auto p-6"> @@ -79,15 +91,27 @@ const FindPage = () => { 고객님과 일치하는 아이디입니다. </p> <p className="text-xl font-semibold mt-2"> ID : {foundId}</p> - <PrimaryButton text="로그인" onClick={() => router.push("/login")} /> - <button className="w-full text-sm underline mt-4" onClick={() => handleTabChange("password")}> + <PrimaryButton + text="로그인" + onClick={() => router.push("/login")} + /> + <button + className="w-full text-sm underline mt-4" + onClick={() => handleTabChange("password")} + > 비밀번호 찾기 </button> </div> ) : ( <div> - <InputField label="이메일" type="email" placeholder="이메일 주소 입력" /> - <PrimaryButton text="아이디 확인하기" onClick={() => setFoundId("ekdm*******")} /> + <InputField + label="이메일" + type="email" + placeholder="이메일 주소 입력" + value={email} + onChange={(e) => setEmail(e.target.value)} + /> + <PrimaryButton text="아이디 확인하기" onClick={handleFindId} /> </div> ) ) : showPasswordReset ? ( @@ -118,14 +142,19 @@ const FindPage = () => { </div> ) : ( <div> - <InputField - label="아이디" - type="text" - placeholder="아이디 입력" - /> + <InputField label="아이디" type="text" placeholder="아이디 입력" /> <div className="w-full flex items-center"> - <InputField label="이메일" type="email" placeholder="이메일 주소 입력"> - <button className="font-bold px-4 py-2 bg-primary text-white rounded-md text-sm whitespace-nowrap" onClick={() => setShowVerification(true)}> + <InputField + label="이메일" + type="email" + placeholder="이메일 주소 입력" + value={email} + onChange={(e) => setEmail(e.target.value)} + > + <button + className="font-bold px-4 py-2 bg-primary text-white rounded-md text-sm whitespace-nowrap" + onClick={() => setShowVerification(true)} + > 인증 </button> </InputField> @@ -145,8 +174,12 @@ const FindPage = () => { verificationError ? "border-red-500" : "border-gray-600" }`} /> - {isVerified && <CheckCircleIcon className="w-6 h-6 text-green-500 flex-shrink-0 ml-2" />} - {verificationError && <XCircleIcon className="w-6 h-6 text-red-500 flex-shrink-0 ml-2" />} + {isVerified && ( + <CheckCircleIcon className="w-6 h-6 text-green-500 flex-shrink-0 ml-2" /> + )} + {verificationError && ( + <XCircleIcon className="w-6 h-6 text-red-500 flex-shrink-0 ml-2" /> + )} <button className="ml-2 font-bold px-4 py-2 bg-primary text-white rounded-md text-sm whitespace-nowrap" onClick={() => { @@ -162,10 +195,18 @@ const FindPage = () => { 확인 </button> </div> - {verificationError && <p className="text-red-500 font-semibold text-sm mt-1">* 인증번호를 다시 확인해주세요.</p>} + {verificationError && ( + <p className="text-red-500 font-semibold text-sm mt-1"> + * 인증번호를 다시 확인해주세요. + </p> + )} </div> )} - <PrimaryButton text="비밀번호 재설정" onClick={() => setShowPasswordReset(true)} disabled={!isVerified} /> + <PrimaryButton + text="비밀번호 재설정" + onClick={() => setShowPasswordReset(true)} + disabled={!isVerified} + /> </div> )} </div> From 6c7383bfcd1f910f0fc5bc5d046aa0ba4bc4faac Mon Sep 17 00:00:00 2001 From: LEE-DA-EUN <delee3064@gmail.com> Date: Mon, 7 Apr 2025 16:10:29 +0900 Subject: [PATCH 9/9] =?UTF-8?q?#26=20feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B0=BE=EA=B8=B0=20=EB=B0=8F=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/find/page.tsx | 86 ++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/find/page.tsx b/frontend/src/app/find/page.tsx index d2ba50c..b5dbb94 100644 --- a/frontend/src/app/find/page.tsx +++ b/frontend/src/app/find/page.tsx @@ -11,6 +11,7 @@ import axios from "@/utils/axios"; const FindPage = () => { const router = useRouter(); const [tab, setTab] = useState<"id" | "password">("id"); + const [username, setUsername] = useState(""); const [email, setEmail] = useState(""); const [foundId, setFoundId] = useState<string | null>(null); const [showVerification, setShowVerification] = useState(false); @@ -45,7 +46,7 @@ const FindPage = () => { const handleFindId = async () => { try { const res = await axios.post("/api/v1/member/find-userId", { email }); - if (res.data.status === 200) { + if (res.data.status === 0) { setFoundId(res.data.data); } else { alert(res.data.message || "아이디를 찾을 수 없습니다."); @@ -56,6 +57,62 @@ const FindPage = () => { } }; + const handleSendResetEmail = async () => { + try { + const res = await axios.post("/api/v1/email/reset-certification", { + username, + email, + }); + if (res.data.status === 200) { + setShowVerification(true); + } else { + alert(res.data.message || "이메일 인증 요청 실패"); + } + } catch (err) { + console.error("이메일 전송 실패", err); + } + }; + + const handleCheckResetCode = async () => { + try { + const res = await axios.post("/api/v1/member/check-certification", { + username, + email, + certificationNumber: verificationCode, + }); + if (res.data.status === 200) { + setIsVerified(true); + setVerificationError(false); + } else { + setIsVerified(false); + setVerificationError(true); + } + } catch (err) { + console.error("인증 실패", err); + setIsVerified(false); + setVerificationError(true); + } + }; + + const handleResetPassword = async () => { + try { + const res = await axios.post("/api/v1/member/reset-password", { + username, + password: newPassword, + confirmPassword, + }); + if (res.data.status === 200) { + alert("비밀번호가 성공적으로 변경되었습니다."); + router.push("/login"); + } else { + alert(res.data.message || "비밀번호 변경 실패"); + } + } catch (err) { + console.error("비밀번호 변경 실패", err); + alert("비밀번호 변경 중 오류가 발생했습니다."); + } + }; + return ( <div className="flex flex-col items-center w-[390px] pt-[164px] h-[844px] mx-auto p-6"> <Title pageTitle="아이디 / 비밀번호 찾기" /> @@ -120,9 +177,9 @@ const FindPage = () => { label="아이디" type="text" placeholder="아이디 입력" + value={username} disabled /> - <InputField label="변경 비밀번호" type="password" @@ -138,11 +195,20 @@ const FindPage = () => { value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} /> - <PrimaryButton text="비밀번호 변경하기" /> + <PrimaryButton + text="비밀번호 변경하기" + onClick={handleResetPassword} + /> </div> ) : ( <div> - <InputField label="아이디" type="text" placeholder="아이디 입력" /> + <InputField + label="아이디" + type="text" + placeholder="아이디 입력" + value={username} + onChange={(e) => setUsername(e.target.value)} + /> <div className="w-full flex items-center"> <InputField label="이메일" @@ -153,7 +219,7 @@ const FindPage = () => { > <button className="font-bold px-4 py-2 bg-primary text-white rounded-md text-sm whitespace-nowrap" - onClick={() => setShowVerification(true)} + onClick={handleSendResetEmail} > 인증 </button> @@ -182,15 +248,7 @@ const FindPage = () => { )} <button className="ml-2 font-bold px-4 py-2 bg-primary text-white rounded-md text-sm whitespace-nowrap" - onClick={() => { - if (verificationCode === "1234") { - setIsVerified(true); - setVerificationError(false); - } else { - setIsVerified(false); - setVerificationError(true); - } - }} + onClick={handleCheckResetCode} > 확인 </button>