diff --git a/src/components/auth/CommonAuthInput.tsx b/src/components/auth/common/CommonAuthInput.tsx similarity index 100% rename from src/components/auth/CommonAuthInput.tsx rename to src/components/auth/common/CommonAuthInput.tsx diff --git a/src/components/auth/OnboardingIntro.tsx b/src/components/auth/common/OnboardingIntro.tsx similarity index 90% rename from src/components/auth/OnboardingIntro.tsx rename to src/components/auth/common/OnboardingIntro.tsx index 92d780b..478077f 100644 --- a/src/components/auth/OnboardingIntro.tsx +++ b/src/components/auth/common/OnboardingIntro.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -import IntroSlide1 from "./intro/IntroSlide1"; -import IntroSlide2 from "./intro/IntroSlide2"; -import IntroSlide3 from "./intro/IntroSlide3"; +import IntroSlide1 from "../intro/IntroSlide1"; +import IntroSlide2 from "../intro/IntroSlide2"; +import IntroSlide3 from "../intro/IntroSlide3"; export default function OnboardingIntro() { const [currentSlide, setCurrentSlide] = useState(0); diff --git a/src/components/auth/findEmailStep/Step01Phone.tsx b/src/components/auth/findEmailStep/Step01Phone.tsx new file mode 100644 index 0000000..8f14f04 --- /dev/null +++ b/src/components/auth/findEmailStep/Step01Phone.tsx @@ -0,0 +1,145 @@ +import { useEffect, useState } from "react"; +import { type SubmitHandler, useForm, useWatch } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { toast } from "sonner"; +import type { z } from "zod"; + +import { findEmailStep01Schema } from "@/utils/validation"; + +import CommonAuthInput from "@/components/auth/common/CommonAuthInput"; +import Button from "@/components/common/Button"; + +import useAuthStore from "@/store/useAuthStore"; + +interface IStep01PhoneProps { + onNext: () => void; +} + +type TStep01FormValues = z.infer; + +export default function Step01Phone({ onNext }: IStep01PhoneProps) { + const navigate = useNavigate(); + const { setEmail } = useAuthStore(); + + const [sendCode, setSendCode] = useState(false); + const [, setCodeVerify] = useState(false); + const [codeError, setCodeError] = useState(""); + + const { + register, + handleSubmit, + control, + trigger, + formState: { errors, isValid }, + } = useForm({ + mode: "onBlur", + resolver: zodResolver(findEmailStep01Schema), + }); + + const watchedPhone = useWatch({ control, name: "phoneNum" }); + const watchedCode = useWatch({ control, name: "code" }); + + const postSendCode = async () => { + setCodeVerify(false); + const isPhoneValid = await trigger("phoneNum"); + if (isPhoneValid && watchedPhone) { + setSendCode(true); + toast.success("인증번호가 발송되었습니다.", { + description: "테스트용: 아무 번호나 입력하세요", + }); + } + }; + + const onSubmit: SubmitHandler = async () => { + // 임시 이메일 + setEmail("smu2021@naver.com"); + onNext(); + }; + + useEffect(() => { + setCodeVerify(false); + setCodeError(""); + }, [watchedCode, watchedPhone]); + + useEffect(() => { + setSendCode(false); + }, [watchedPhone]); + + return ( +
+
+

+

이메일 찾기를 위해

+

휴대폰 인증을 진행할게요

+

+ +
+ {!sendCode ? ( +
+
+ +
+ +
+ ) : ( + + )} + + +
+ +
+ +
+ +
+ +
+
+
+ ); +} diff --git a/src/components/auth/findEmailStep/Step02Email.tsx b/src/components/auth/findEmailStep/Step02Email.tsx new file mode 100644 index 0000000..e3d6f4f --- /dev/null +++ b/src/components/auth/findEmailStep/Step02Email.tsx @@ -0,0 +1,50 @@ +import { useNavigate } from "react-router-dom"; + +import { maskEmail } from "@/utils/maskEmail"; + +import Button from "@/components/common/Button"; + +import useAuthStore from "@/store/useAuthStore"; + +export default function Step02Email() { + const navigate = useNavigate(); + const { email } = useAuthStore(); + + return ( +
+
+

+

입력하신 정보로

+

WYA에 가입된 계정을 찾았어요

+

+ +
+
+ + 이메일 ID + + + {maskEmail(email)} + +
+ +
+ + +
+
+ ); +} diff --git a/src/components/auth/signupStep/Step01Email.tsx b/src/components/auth/signupStep/Step01Email.tsx index b64674b..8b25659 100644 --- a/src/components/auth/signupStep/Step01Email.tsx +++ b/src/components/auth/signupStep/Step01Email.tsx @@ -6,7 +6,7 @@ import type { z } from "zod"; import { step01Schema } from "@/utils/validation"; -import CommonAuthInput from "@/components/auth/CommonAuthInput"; +import CommonAuthInput from "@/components/auth/common/CommonAuthInput"; import Button from "@/components/common/Button"; import useAuthStore from "@/store/useAuthStore"; diff --git a/src/components/auth/signupStep/Step02Password.tsx b/src/components/auth/signupStep/Step02Password.tsx index 4b15eea..eb70668 100644 --- a/src/components/auth/signupStep/Step02Password.tsx +++ b/src/components/auth/signupStep/Step02Password.tsx @@ -4,7 +4,7 @@ import type { z } from "zod"; import { step02Schema } from "@/utils/validation"; -import CommonAuthInput from "@/components/auth/CommonAuthInput"; +import CommonAuthInput from "@/components/auth/common/CommonAuthInput"; import Button from "@/components/common/Button"; import useAuthStore from "@/store/useAuthStore"; diff --git a/src/components/auth/signupStep/Step03Profile.tsx b/src/components/auth/signupStep/Step03Profile.tsx index a59fe34..5885ee9 100644 --- a/src/components/auth/signupStep/Step03Profile.tsx +++ b/src/components/auth/signupStep/Step03Profile.tsx @@ -6,7 +6,7 @@ import type { z } from "zod"; import { step03Schema } from "@/utils/validation"; -import CommonAuthInput from "@/components/auth/CommonAuthInput"; +import CommonAuthInput from "@/components/auth/common/CommonAuthInput"; import Button from "@/components/common/Button"; import { MODAL_TYPES } from "@/components/modal/ModalProvider"; diff --git a/src/index.css b/src/index.css index 2087eb6..6e359ec 100644 --- a/src/index.css +++ b/src/index.css @@ -199,4 +199,37 @@ button { outline: 2px solid #0084fe; outline-offset: 2px; } + + /* Float Animation */ + @keyframes float { + 0% { + transform: translateY(0px); + } + 50% { + transform: translateY(-20px); + } + 100% { + transform: translateY(0px); + } + } + + .animate-float { + animation: float 6s ease-in-out infinite; + } + + /* Fade In Up */ + @keyframes fade-in-up { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + .animate-fade-in-up { + animation: fade-in-up 0.8s ease-out forwards; + } } diff --git a/src/layout/auth/AuthLayout.tsx b/src/layout/auth/AuthLayout.tsx index fd1f974..73c2a82 100644 --- a/src/layout/auth/AuthLayout.tsx +++ b/src/layout/auth/AuthLayout.tsx @@ -1,6 +1,6 @@ import { Outlet } from "react-router-dom"; -import OnboardingIntro from "@/components/auth/OnboardingIntro"; +import OnboardingIntro from "@/components/auth/common/OnboardingIntro"; export default function AuthLayout() { return ( diff --git a/src/pages/auth/FindEmail.tsx b/src/pages/auth/FindEmail.tsx new file mode 100644 index 0000000..a1f5cf9 --- /dev/null +++ b/src/pages/auth/FindEmail.tsx @@ -0,0 +1,19 @@ +import { useState } from "react"; + +import Step01Phone from "@/components/auth/findEmailStep/Step01Phone"; +import Step02Email from "@/components/auth/findEmailStep/Step02Email"; + +export default function FindEmail() { + const [step, setStep] = useState(1); + + const handleNextStep = () => { + setStep((prev) => prev + 1); + }; + + return ( + <> + {step === 1 && } + {step === 2 && } + + ); +} diff --git a/src/pages/auth/Login.tsx b/src/pages/auth/Login.tsx index 6a630e9..ad65fa4 100644 --- a/src/pages/auth/Login.tsx +++ b/src/pages/auth/Login.tsx @@ -6,7 +6,7 @@ import type { z } from "zod"; import { loginSchema } from "@/utils/validation"; -import CommonAuthInput from "@/components/auth/CommonAuthInput"; +import CommonAuthInput from "@/components/auth/common/CommonAuthInput"; import Button from "@/components/common/Button"; import GoogleIcon from "@/assets/auth/social/google.svg?react"; @@ -36,7 +36,7 @@ export default function Login() { return (
-

+

로그인

@@ -59,7 +59,7 @@ export default function Login() { /> 이메일/비밀번호를 잊어버렸어요 diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index c4ed4c3..b957f29 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -4,6 +4,7 @@ import { createBrowserRouter, Navigate } from "react-router-dom"; import AuthLayout from "@/layout/auth/AuthLayout"; import Layout from "@/layout/common/Layout"; import RootLayout from "@/layout/common/RootLayout"; +import FindEmail from "@/pages/auth/FindEmail"; import FindPw from "@/pages/auth/FindPw"; import Login from "@/pages/auth/Login"; import Signup from "@/pages/auth/Signup"; @@ -68,6 +69,10 @@ const router = createBrowserRouter([ path: "login", element: , }, + { + path: "find-email", + element: , + }, { path: "find-pw", element: , diff --git a/src/utils/maskEmail.ts b/src/utils/maskEmail.ts new file mode 100644 index 0000000..4c6bcc8 --- /dev/null +++ b/src/utils/maskEmail.ts @@ -0,0 +1,12 @@ +export function maskEmail(email: string) { + if (!email) return ""; + + const [local, domain] = email.split("@"); + if (!local || !domain) return email; + + if (local.length <= 3) { + return `${local}****@${domain}`; + } + + return `${local.slice(0, 3)}****@${domain}`; +} diff --git a/src/utils/validation.tsx b/src/utils/validation.tsx index 6d4b59f..c3f3846 100644 --- a/src/utils/validation.tsx +++ b/src/utils/validation.tsx @@ -52,11 +52,13 @@ export const signupSchema = z export const termsSchema = z.boolean().refine((val) => val === true); +// 로그인 export const loginSchema = z.object({ email: emailSchema, password: passwordSchema, }); +// 단계별 회원가입 export const step01Schema = z.object({ email: emailSchema, code: codeSchema, @@ -78,3 +80,9 @@ export const step03Schema = z.object({ terms: termsSchema, marketing: z.boolean().optional(), }); + +// 이메일 찾기1 - 휴대폰 번호 인증 +export const findEmailStep01Schema = z.object({ + phoneNum: phoneSchema, + code: codeSchema, +});