Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/api/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type {
IEmailSendRequest,
IEmailSendResponse,
IEmailVerifyRequest,
ISignUpRequest,
ISignUpResponse,
} from "../../types/auth/auth";

import { axiosInstance } from "@/lib/axiosInstance";
Expand All @@ -27,3 +29,14 @@ export const verifyEmail = async ({
});
return data;
};

// 단순 회원가입
export const signUp = async (
data: ISignUpRequest,
): Promise<ICommonResponse<ISignUpResponse>> => {
const { data: responseData } = await axiosInstance.post(
"/api/users/signup",
data,
);
return responseData;
};
137 changes: 36 additions & 101 deletions src/components/auth/signupStep/Step01Email.tsx
Original file line number Diff line number Diff line change
@@ -1,105 +1,30 @@
import { useCallback, useEffect, useState } from "react";
import { type SubmitHandler, useForm, useWatch } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { toast } from "sonner";
import type { z } from "zod";

import { step01Schema } from "@/utils/validation";

import { useAuth } from "@/hooks/auth/useAuth";
import { useTimer } from "@/hooks/useTimer";
import { useEmailVerification } from "@/hooks/auth/useEmailVerification";

import CommonAuthInput from "@/components/auth/common/CommonAuthInput";
import Button from "@/components/common/Button";

import useAuthStore from "@/store/useAuthStore";

interface IStep01EmailProps {
onNext: () => void;
}

type TStep01FormValues = z.infer<typeof step01Schema>;

export default function SignupEmail({ onNext }: IStep01EmailProps) {
const { setEmail } = useAuthStore();
const { useSendCode, useCheckCode } = useAuth();

const [sendCode, setSendCode] = useState(false);
const [, setCodeVerify] = useState(false);
const [codeError, setCodeError] = useState("");

const {
register,
handleSubmit,
control,
trigger,
formState: { errors, isValid },
} = useForm<TStep01FormValues>({
mode: "onBlur",
resolver: zodResolver(step01Schema),
});

const watchedEmail = useWatch({ control, name: "email" });
const watchedCode = useWatch({ control, name: "code" });

const { formattedTime, restart, stop, isExpired } = useTimer(180, {
onExpire: () => {
toast.error("인증 시간이 만료되었습니다. 다시 시도해주세요.");
form: { register },
watchedEmail,
sendCode,
formattedTime,
isExpired,
codeError,
errors,
isValid,
isPending,
handlers: {
postSendCode,
handleResendEmail,
handleEditEmail,
handleSubmit,
},
});

const resetVerification = useCallback(() => {
setSendCode(false);
stop();
}, [stop]);

const postSendCode = async () => {
setCodeVerify(false);
const isEmailValid = await trigger("email");
if (isEmailValid && watchedEmail) {
useSendCode.mutate(
{ email: watchedEmail },
{
onSuccess: () => {
setSendCode(true);
toast.success("인증번호가 발송되었습니다.");
restart();
},
onError: (error) => {
toast.error(
error.response?.data?.message || "메일 발송에 실패했습니다.",
);
},
},
);
}
};

const onSubmit: SubmitHandler<TStep01FormValues> = async (data) => {
useCheckCode.mutate(
{ email: data.email, authCode: data.code },
{
onSuccess: () => {
setEmail(data.email);
onNext();
},
onError: (error) => {
setCodeError(
error.response?.data?.message || "인증번호가 올바르지 않습니다.",
);
},
},
);
};

useEffect(() => {
setCodeVerify(false);
setCodeError("");
}, [watchedCode, watchedEmail]);

useEffect(() => {
resetVerification();
}, [watchedEmail, resetVerification]);
} = useEmailVerification({ onNext });

return (
<div className="w-full min-h-screen bg-white flex items-center justify-center">
Expand All @@ -126,18 +51,28 @@ export default function SignupEmail({ onNext }: IStep01EmailProps) {
className="shrink-0 h-13.5! border border-brand-400 text-status-blue bg-white hover:bg-gray-50 px-4 rounded-15 font-body2 whitespace-nowrap"
onClick={postSendCode}
type="button"
disabled={useSendCode.isPending}
disabled={isPending}
>
인증번호 받기
</Button>
</div>
) : (
<CommonAuthInput
type="text"
value={watchedEmail || ""}
readOnly
className="w-full h-13.5 px-5 border rounding-15 text-body1 text-text-main bg-white border-brand-400 focus:outline-none focus:border-brand-400"
/>
<div className="relative w-full">
<CommonAuthInput
type="text"
value={watchedEmail || ""}
readOnly
className="w-full h-13.5 px-5 border rounding-15 text-body1 text-text-main bg-white border-brand-400 focus:outline-none focus:border-brand-400"
/>
<button
type="button"
onClick={handleEditEmail}
aria-label="이메일 수정"
className="absolute right-5 top-1/2 -translate-y-1/2 font-body2 text-text-placeholder underline hover:text-text-main"
>
수정
</button>
</div>
)}

<CommonAuthInput
Expand All @@ -163,9 +98,9 @@ export default function SignupEmail({ onNext }: IStep01EmailProps) {
<Button
size="big"
fullWidth
onClick={handleSubmit(onSubmit)}
onClick={handleSubmit}
variant="gradient"
disabled={!isValid || useCheckCode.isPending || isExpired}
disabled={!isValid || isPending || isExpired}
>
다음으로
</Button>
Expand All @@ -175,7 +110,7 @@ export default function SignupEmail({ onNext }: IStep01EmailProps) {
<div className="mt-6 flex justify-center">
<button
type="button"
onClick={resetVerification}
onClick={handleResendEmail}
className="font-body2 text-text-placeholder underline underline-offset-4 hover:text-text-auth-sub"
>
인증번호 다시 받기
Expand Down
43 changes: 27 additions & 16 deletions src/components/auth/signupStep/Step03Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type { z } from "zod";

import { step03Schema } from "@/utils/validation";

import { useAuth } from "@/hooks/auth/useAuth";

import CommonAuthInput from "@/components/auth/common/CommonAuthInput";
import Button from "@/components/common/Button";
import { MODAL_TYPES } from "@/components/modal/ModalProvider";
Expand All @@ -17,8 +19,9 @@ type TStep03FormValues = z.infer<typeof step03Schema>;

export default function SignupProfile() {
const navigate = useNavigate();
const { email, password } = useAuthStore();
const { email, password, resetAuth } = useAuthStore();
const { openModal } = useModalStore();
const { useSignUp } = useAuth();

const {
register,
Expand All @@ -33,19 +36,28 @@ export default function SignupProfile() {
});

const onSubmit: SubmitHandler<TStep03FormValues> = (data) => {
// 최종 회원가입 데이터 (예시)
const finalSignupData = {
email,
password,
...data,
};

console.log("Final Signup Data:", finalSignupData);

toast.success("회원가입이 완료되었습니다!", {
description: `이름: ${data.name}, 환영합니다!`,
});
navigate("/login");
useSignUp.mutate(
{
email,
password,
name: data.name,
phone_number: data.phoneNum,
},
{
onSuccess: () => {
toast.success("회원가입이 완료되었습니다!", {
description: `${data.name}님, 환영합니다!`,
});
resetAuth();
navigate("/login");
},
onError: (error) => {
toast.error(
error.response?.data?.message || "회원가입에 실패했습니다.",
);
},
},
);
};

return (
Expand Down Expand Up @@ -94,7 +106,6 @@ export default function SignupProfile() {
setValue("marketing", agreements.marketing);
if (agreements.privacy) {
setValue("terms", true, { shouldValidate: true });
toast.success("약관에 동의하였습니다.");
} else {
setValue("terms", false, { shouldValidate: true });
}
Expand Down Expand Up @@ -129,7 +140,7 @@ export default function SignupProfile() {
fullWidth
type="submit"
variant="gradient"
disabled={!isValid}
disabled={!isValid || useSignUp.isPending}
>
회원 가입
</Button>
Expand Down
2 changes: 2 additions & 0 deletions src/constants/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// 인증번호 입력 시간 상수화
export const AUTH_TIMER_DURATION = 180;
4 changes: 3 additions & 1 deletion src/hooks/auth/useAuth.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useCoreMutation } from "@/hooks/customQuery";

import { sendEmail, verifyEmail } from "@/api/auth/auth";
import { sendEmail, signUp, verifyEmail } from "@/api/auth/auth";

export const useAuth = () => {
const useSendCode = useCoreMutation(sendEmail);
const useCheckCode = useCoreMutation(verifyEmail);
const useSignUp = useCoreMutation(signUp);

return {
useSendCode,
useCheckCode,
useSignUp,
};
};
Loading