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: 12 additions & 1 deletion app/register/_components/ScreenRegister.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,22 @@

const { mutate: sendEmail, isPending: isSendingEmail } = useSendEmail();
const { verify, isPending: isVerifyingEmail } = useVerifyEmail();
const { mutate: signUp, isPending: isSigningUp } = useSignUp();

Check warning on line 22 in app/register/_components/ScreenRegister.tsx

View workflow job for this annotation

GitHub Actions / lint

'isSigningUp' is assigned a value but never used

const handleEmailSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
setVerificationCode("");
setStep(2);

sendEmail(email, {
onSuccess: () => setStep(2),
onError: (error) => {
const message =
axios.isAxiosError(error) && error.response?.data?.message
? error.response.data.message
: "이메일 전송에 실패했습니다. 다시 시도해주세요.";
alert(message);
setStep(1);
},
});
};
Comment on lines 24 to 39
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

이메일 전송 API 호출이 성공했는지 여부와 관계없이 setStep(2)를 호출하여 다음 단계로 즉시 이동시키는 것은 사용자에게 혼란을 줄 수 있습니다. 예를 들어, 네트워크가 불안정하여 API 응답이 늦어지는 경우 사용자는 다음 화면으로 넘어갔다가 오류 알림과 함께 이전 화면으로 되돌아오는 부정적인 경험을 할 수 있습니다.

useMutationonSuccess 콜백을 사용하여 API 호출이 성공적으로 완료되었을 때만 다음 단계로 이동하도록 로직을 수정하는 것이 좋습니다. 이렇게 하면 onError 핸들러에서 setStep(1)로 되돌리는 불필요한 코드도 제거할 수 있어 코드가 더 명확해집니다.

Suggested change
const handleEmailSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
setVerificationCode("");
setStep(2);
sendEmail(email, {
onSuccess: () => setStep(2),
onError: (error) => {
const message =
axios.isAxiosError(error) && error.response?.data?.message
? error.response.data.message
: "이메일 전송에 실패했습니다. 다시 시도해주세요.";
alert(message);
setStep(1);
},
});
};
const handleEmailSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
setVerificationCode("");
sendEmail(email, {
onSuccess: () => {
setStep(2);
},
onError: (error) => {
const message =
axios.isAxiosError(error) && error.response?.data?.message
? error.response.data.message
: "이메일 전송에 실패했습니다. 다시 시도해주세요.";
alert(message);
},
});
};
References
  1. The suggested code correctly uses the native alert() function for displaying error messages, aligning with the preference over toast notifications.


Expand Down Expand Up @@ -86,6 +96,7 @@
onVerificationCodeChange={setVerificationCode}
onVerify={handleVerify}
onResend={handleResendCode}
isSending={isSendingEmail}
isVerifying={isVerifyingEmail}
/>
)}
Expand Down
49 changes: 31 additions & 18 deletions app/register/_components/VerificationStep.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Button from "@/components/ui/Button";
import FormInput from "@/components/ui/FormInput";
import React, { useState, useEffect } from "react";
import React, { useEffect, useState } from "react";

type VerificationStepProps = {
email: string;
verificationCode: string;
onVerificationCodeChange: (code: string) => void;
onVerify: (code: string, onError: (msg?: string) => void) => void;
onResend: () => void;
isSending?: boolean;
isVerifying?: boolean;
};

Expand All @@ -17,10 +18,11 @@ export const VerificationStep = ({
onVerificationCodeChange,
onVerify,
onResend,
isSending = false,
isVerifying = false,
}: VerificationStepProps) => {
const [timeLeft, setTimeLeft] = useState(300); // 5분 = 300초
const [resendCooldown, setResendCooldown] = useState(0); // 재전송 쿨다운 (초)
const [timeLeft, setTimeLeft] = useState(300);
const [resendCooldown, setResendCooldown] = useState(0);
const [errorMessage, setErrorMessage] = useState("");

useEffect(() => {
Expand All @@ -46,7 +48,10 @@ export const VerificationStep = ({
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;

return `${mins.toString().padStart(2, "0")}:${secs
.toString()
.padStart(2, "0")}`;
};

const handleVerificationCodeChange = (value: string) => {
Expand All @@ -59,20 +64,20 @@ export const VerificationStep = ({
const handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
onVerify(verificationCode, (msg?: string) =>
setErrorMessage(msg || "인증번호를 다시 확인해 주세요"),
setErrorMessage(msg || "인증번호를 다시 확인해 주세요."),
);
};

const handleResend = () => {
setTimeLeft(300); // 타이머 리셋
setResendCooldown(180); // 재전송 쿨다운 3분
setErrorMessage(""); // 에러 상태 초기화
setTimeLeft(300);
setResendCooldown(180);
setErrorMessage("");
onResend();
};
Comment on lines 71 to 76
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Resend cooldown on failure 🐞 Bug ⛯ Reliability

VerificationStep sets resendCooldown to 180 seconds before attempting onResend, but ScreenRegister’s
resend handler only alerts on sendEmail error and never clears the cooldown. If resend fails, the
user is prevented from retrying resend for 3 minutes even though no new code was sent.
Agent Prompt
### Issue description
`VerificationStep` applies `resendCooldown(180)` optimistically before the resend request outcome is known. When resend fails, the parent only shows an alert and does not reset the cooldown, locking the user out from retrying.

### Issue Context
- `VerificationStep.handleResend()` sets `resendCooldown` before calling `onResend()`.
- `ScreenRegister.handleResendCode()` triggers `sendEmail` and only alerts on error.

### Fix Focus Areas
- app/register/_components/VerificationStep.tsx[71-76]
- app/register/_components/ScreenRegister.tsx[75-79]
- app/register/_components/VerificationStep.tsx[125-136]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


const maskEmail = (email: string) => {
const [local, domain] = email.split("@");
if (!local || !domain) return email;
const maskEmail = (value: string) => {
const [local, domain] = value.split("@");
if (!local || !domain) return value;

const maskedLocal = local[0] + "*".repeat(local.length - 1);

Expand All @@ -86,10 +91,12 @@ export const VerificationStep = ({
STEP 1 <span className="text-gray-300">/ 2</span>
</span>
<h1 className="typo-28-600 mt-4 mb-1 text-black">
인증번호를 입력해주세요
인증번호를 입력해 주세요
</h1>
<h2 className="typo-16-500 text-gray-600">
{maskEmail(email)}로 인증번호를 보냈어요
{isSending
? `${maskEmail(email)}로 인증번호를 보내는 중이에요`
: `${maskEmail(email)}로 인증번호를 보냈어요`}
</h2>
</div>

Expand Down Expand Up @@ -118,11 +125,15 @@ export const VerificationStep = ({
<Button
type="button"
onClick={handleResend}
disabled={resendCooldown > 0}
disabled={resendCooldown > 0 || isSending}
className="bg-bubble-background-white text-color-text-caption1 typo-18-600 h-[48px] w-[120px] shrink-0"
shadow={false}
>
{resendCooldown > 0 ? formatTime(resendCooldown) : "재전송"}
{isSending
? "전송 중.."
: resendCooldown > 0
? formatTime(resendCooldown)
: "재전송"}
</Button>
</div>
{errorMessage && (
Expand All @@ -131,20 +142,22 @@ export const VerificationStep = ({
</span>
)}
</div>

<div className="mt-auto flex w-full flex-col gap-4">
<div className="bg-color-gray-50-a80 w-full rounded-[16px] border-[#b3b3b34d] p-4 leading-0">
<span className="typo-12-500 text-color-text-caption3">
인증번호가 오지 않았나요?
<br /> 이전 단계에서 이메일을 재확인하거나, 스팸함을 확인해주세요.
<br /> 이전 단계에서 이메일을 재확인하거나, 스팸함을 확인해
주세요.
</span>
</div>

<Button
shadow={true}
type="submit"
disabled={!verificationCode.trim() || isVerifying}
disabled={!verificationCode.trim() || isVerifying || isSending}
>
{isVerifying ? "확인 중..." : "인증확인"}
{isVerifying ? "확인 중.." : "인증확인"}
</Button>
</div>
</form>
Expand Down
Loading