Files
xrlms/src/app/menu/ChangePasswordModal.tsx
2025-11-18 07:53:09 +09:00

227 lines
8.5 KiB
TypeScript

'use client';
import { useEffect, useState } from "react";
import ModalCloseSvg from "../svgs/closexsvg";
type Props = {
open: boolean;
onClose: () => void;
onSubmit?: (payload: { email: string; code?: string; newPassword: string }) => void;
showVerification?: boolean;
devVerificationState?: 'initial' | 'sent' | 'verified' | 'failed';
};
export default function ChangePasswordModal({ open, onClose, onSubmit, showVerification = false, devVerificationState }: Props) {
const [email, setEmail] = useState("xrlms2025@gmail.com");
const [code, setCode] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [error, setError] = useState<string | null>(null); // 인증번호 오류 등
const [requireCode, setRequireCode] = useState<boolean>(showVerification);
const [isCodeSent, setIsCodeSent] = useState<boolean>(showVerification);
const canConfirm = code.trim().length > 0;
const [isVerified, setIsVerified] = useState(false);
const hasError = !!error;
// 외부에서 전달된 개발모드 상태(devVerificationState)에 따라 UI 동기화
useEffect(() => {
if (!devVerificationState) return;
switch (devVerificationState) {
case 'initial':
setRequireCode(false);
setIsCodeSent(false);
setCode("");
setError(null);
setIsVerified(false);
break;
case 'sent':
setRequireCode(true);
setIsCodeSent(true);
setCode("");
setError(null);
setIsVerified(false);
break;
case 'verified':
setRequireCode(true);
setIsCodeSent(true);
setCode("123456");
setError(null);
setIsVerified(true);
break;
case 'failed':
setRequireCode(true);
setIsCodeSent(true);
setCode("");
setError("올바르지 않은 인증번호입니다. 인증번호를 확인해주세요.");
setIsVerified(false);
break;
default:
break;
}
}, [devVerificationState]);
if (!open) return null;
const handleSubmit = () => {
setError(null);
if (requireCode) {
if (!code) {
setError("인증번호를 입력해 주세요.");
return;
}
}
if (!newPassword || !confirmPassword) {
setError("새 비밀번호를 입력해 주세요.");
return;
}
if (newPassword !== confirmPassword) {
setError("새 비밀번호가 일치하지 않습니다.");
return;
}
onSubmit?.({ email, code: requireCode ? code : undefined, newPassword });
onClose();
};
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-[2px]"
role="dialog"
aria-modal="true"
>
<div className="w-[480px] rounded-[12px] border border-[#dee1e6] bg-white shadow-[0_10px_30px_rgba(0,0,0,0.06)]">
{/* header */}
<div className="flex items-center justify-between p-6">
<h2 className="text-[20px] font-bold leading-[1.5] text-[#333c47]"> </h2>
<button
type="button"
aria-label="닫기"
onClick={onClose}
className="inline-flex size-6 items-center justify-center text-[#333c47] hover:opacity-80 cursor-pointer"
>
<ModalCloseSvg />
</button>
</div>
{/* body */}
<div className="flex flex-col gap-[10px] px-6">
<div className="flex flex-col gap-2">
<label className="w-[100px] text-[15px] font-semibold leading-[1.5] text-[#6c7682]"> ()</label>
<div className="flex items-center gap-3">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className={[
"h-10 flex-1 rounded-[8px] border border-[#dee1e6] px-3 text-[16px] leading-[1.5] text-[#333c47] placeholder-[#b1b8c0] outline-none",
hasError ? "bg-white" : isCodeSent ? "bg-neutral-50" : "bg-white",
].join(" ")}
placeholder="이메일"
/>
<button
type="button"
onClick={() => {
setRequireCode(true);
setIsCodeSent(true);
setError(null);
}}
className="h-10 w-[136px] rounded-[8px] bg-[#f1f3f5] px-3 text-[16px] font-semibold leading-[1.5] text-[#333c47] cursor-pointer"
>
{isCodeSent ? "인증번호 재전송" : "인증번호 전송"}
</button>
</div>
</div>
{requireCode ? (
<div className="flex flex-col gap-2">
<div className="w-[100px] text-[15px] font-semibold leading-[1.5] text-[#6c7682]"></div>
<div className="flex items-center gap-3">
<input
type="text"
value={code}
onChange={(e) => setCode(e.target.value)}
className="h-10 flex-1 rounded-[8px] border border-[#dee1e6] bg-white px-3 text-[16px] leading-[1.5] text-[#333c47] placeholder-[#b1b8c0] outline-none"
placeholder="인증번호를 입력해 주세요."
/>
<button
type="button"
disabled={!canConfirm}
className={[
"h-10 w-[136px] rounded-[8px] px-3 text-[16px] font-semibold leading-[1.5] cursor-pointer disabled:cursor-default",
canConfirm ? "bg-[#f1f3f5] text-[#4c5561]" : "bg-gray-50 text-[#b1b8c0]",
].join(" ")}
>
</button>
</div>
{isCodeSent && !hasError && !isVerified ? (
<div className="px-1">
<p className="text-[13px] font-semibold leading-[1.4] text-[#384fbf]">
.
</p>
<p className="text-[13px] font-semibold leading-[1.4] text-[#384fbf]">
.
</p>
</div>
) : null}
{error ? (
<p className="px-1 text-[13px] font-semibold leading-[1.4] text-[#f64c4c]">
{error}
</p>
) : null}
</div>
) : null}
<div className="flex flex-col gap-2">
<label className="text-[15px] font-semibold leading-[1.5] text-[#6c7682]"> </label>
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
disabled={!isVerified}
className={[
"h-10 rounded-[8px] border border-[#dee1e6] px-3 text-[16px] leading-[1.5] text-[#333c47] placeholder-[#b1b8c0] outline-none",
isVerified ? "bg-white" : "bg-neutral-50",
].join(" ")}
placeholder="새 비밀번호"
/>
</div>
<div className="flex flex-col gap-2">
<label className="text-[15px] font-semibold leading-[1.5] text-[#6c7682]">
</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={!isVerified}
className={[
"h-10 rounded-[8px] border border-[#dee1e6] px-3 text-[16px] leading-[1.5] text-[#333c47] placeholder-[#b1b8c0] outline-none",
isVerified ? "bg-white" : "bg-neutral-50",
].join(" ")}
placeholder="새 비밀번호 확인"
/>
</div>
</div>
{/* footer */}
<div className="flex items-center justify-center gap-3 p-6">
<button
type="button"
onClick={onClose}
className="h-12 w-[136px] rounded-[10px] bg-[#f1f3f5] px-4 text-[16px] font-semibold leading-[1.5] text-[#4c5561] cursor-pointer"
>
</button>
<button
type="button"
onClick={handleSubmit}
className="h-12 w-[136px] rounded-[10px] bg-[#8598e8] px-4 text-[16px] font-semibold leading-[1.5] text-white cursor-pointer"
>
</button>
</div>
</div>
</div>
);
}