diff --git a/apps/dm/app/(with-layout)/apply/dmdco/page.tsx b/apps/dm/app/(with-layout)/apply/dmdco/page.tsx index 4e8dc5195..02e929a26 100644 --- a/apps/dm/app/(with-layout)/apply/dmdco/page.tsx +++ b/apps/dm/app/(with-layout)/apply/dmdco/page.tsx @@ -4,6 +4,7 @@ "use client"; import { useEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from "react"; +import { useRouter } from "next/navigation"; import SubPageHeader from "@/components/SubPageHeader"; import PostcodeModal from "@/components/PostcodeModal"; import type { AddrItem } from "@/app/store/sdmMng/postcode-service"; @@ -162,6 +163,7 @@ function Hr({ className = "" }: { className?: string }) { } export default function DemandPlaceRegisterPage() { + const router = useRouter(); const { Common, initializeCommonData } = useDmStore((state) => state); const { selectSidoData, selectSigunData } = Common; @@ -184,6 +186,9 @@ export default function DemandPlaceRegisterPage() { const detailAddrRef = useRef(null); const [tel, setTel] = useState(""); + const [currentPw, setCurrentPw] = useState(""); + const [isCurrentPwVerified, setIsCurrentPwVerified] = useState(false); + const [isCheckingCurrentPw, setIsCheckingCurrentPw] = useState(false); const [pw, setPw] = useState(""); const [pw2, setPw2] = useState(""); @@ -248,6 +253,11 @@ export default function DemandPlaceRegisterPage() { return () => clearTimeout(timer); }, [addr1, focusDetailAddrOnClose, isPostcodeModalOpen]); + useEffect(() => { + if (!editMode) return; + setIsCurrentPwVerified(false); + }, [bizNo, workerNo, noWorkerNo, editMode]); + const sidoOptions = useMemo(() => { const mapped = selectSidoData .filter((item) => item.val !== "0") @@ -285,6 +295,57 @@ export default function DemandPlaceRegisterPage() { } }; + const checkCurrentPassword = async () => { + const bizDigits = bizNo.replace(/\D/g, "").slice(0, 10); + const workerDigits = workerNo.replace(/\D/g, "").slice(0, 4); + const password = currentPw.trim(); + + if (!bizDigits) return alert("사업자등록번호를 입력해 주세요."); + if (!/^\d{10}$/.test(bizDigits)) { + return alert("사업자등록번호는 10자리 숫자만 입력 가능합니다."); + } + if (!noWorkerNo) { + if (!workerDigits) return alert("종사업자등록번호를 입력해 주세요."); + if (!/^\d{4}$/.test(workerDigits)) { + return alert("종사업자등록번호는 4자리 숫자만 입력 가능합니다."); + } + } + if (!password) return alert("기존 비밀번호를 입력해 주세요."); + + const fullDmdInfoId = bizDigits + (noWorkerNo ? "0000" : workerDigits); + const payload = { + dmdInfoId: fullDmdInfoId, + dmdAplyPswd: password, + }; + + try { + setIsCheckingCurrentPw(true); + const response = await postFetch("/reuseFc/request/checkPassword", payload); + const rawResult = await response.json().catch(() => null); + const checkedCount = + typeof rawResult === "number" + ? rawResult + : typeof rawResult?.data === "number" + ? rawResult.data + : Number(rawResult ?? 0); + + if (checkedCount >= 1) { + setIsCurrentPwVerified(true); + alert("기존 비밀번호가 확인되었습니다."); + return; + } + + setIsCurrentPwVerified(false); + alert("기존 비밀번호가 일치하지 않습니다."); + } catch (error) { + setIsCurrentPwVerified(false); + console.error("Failed to check current password", error); + alert("기존 비밀번호 확인에 실패하였습니다."); + } finally { + setIsCheckingCurrentPw(false); + } + }; + const onSubmit = async () => { const bizDigits = bizNo.replace(/\D/g, "").slice(0, 10); const workerDigits = workerNo.replace(/\D/g, "").slice(0, 4); @@ -327,8 +388,15 @@ export default function DemandPlaceRegisterPage() { } if (!passwordConfirm) return alert("비밀번호 확인을 입력해 주세요."); if (password !== passwordConfirm) return alert("비밀번호가 일치하지 않습니다."); - } else if ((password || passwordConfirm) && password !== passwordConfirm) { - return alert("비밀번호가 일치하지 않습니다."); + } else { //수정 + if (password || passwordConfirm) { + if (!password) return alert("비밀번호를 입력해 주세요."); + if (password.length < 8 || password.length > 16) { + return alert("비밀번호는 8자 이상 16자 이내로 입력해주세요."); + } + if (!passwordConfirm) return alert("비밀번호 확인을 입력해 주세요."); + if (password !== passwordConfirm) return alert("비밀번호가 일치하지 않습니다."); + } } const fullDmdInfoId = bizDigits + (noWorkerNo ? "0000" : workerDigits); @@ -336,6 +404,8 @@ export default function DemandPlaceRegisterPage() { if (!editMode) { const isDuplicate = await checkDuplicate(fullDmdInfoId); if (isDuplicate) return alert("이미 등록된 사업자등록번호입니다."); + } else if (!isCurrentPwVerified) { + return alert("기존 비밀번호 확인 후 수정해 주세요."); } const myUUID = getCookie(TOKEN_ID_COOKIE) || FALLBACK_UUID; @@ -374,11 +444,24 @@ export default function DemandPlaceRegisterPage() { alert("수요처 수정 완료!"); } else { await postFetch("/reuseFc/request", payload); + + if (typeof window !== "undefined") { + const nextSessionUser = { + ...(sessionUser ?? {}), + ...payload, + }; + delete nextSessionUser.dmdAplyPswd; + sessionStorage.setItem("user", JSON.stringify(nextSessionUser)); + } + alert("수요처 등록 완료!"); + router.push("/apply/register"); } setPw(""); setPw2(""); + setCurrentPw(""); + setIsCurrentPwVerified(false); } catch (error) { console.error("Failed to submit demand place", error); alert(editMode ? "수요처 수정에 실패하였습니다." : "수요처 등록에 실패하였습니다."); @@ -563,6 +646,41 @@ export default function DemandPlaceRegisterPage() { + {editMode ? ( + <> + +
+
+
+ { + setCurrentPw(value); + setIsCurrentPwVerified(false); + }} + placeholder="기존 비밀번호를 입력하세요." + type="password" + /> +
+ +
+

+ {isCurrentPwVerified ? "기존 비밀번호 확인이 완료되었습니다." : "수정 전 기존 비밀번호 확인이 필요합니다."} +

+
+
+ + + + ) : null} +
(value: T, delay = 250) { export default function PostcodeModal({ open, onClose, onSelect }: Props) { const [mounted, setMounted] = useState(false); - const [q, setQ] = useState(""); + const [q, setQ] = useState(""); // 주소 입력값 const dq = useDebouncedValue(q, 250); - const [items, setItems] = useState([]); - const [suggestions, setSuggestions] = useState([]); + const [items, setItems] = useState([]); // 검색 결과 + const [suggestions, setSuggestions] = useState([]); // 추천 주소 목록 const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); - const [searchMessage, setSearchMessage] = useState(""); + const [searchMessage, setSearchMessage] = useState(""); // 검색 실패/형식 오류/보조 안내 문구를 담는 상태값 const [isInputFocused, setIsInputFocused] = useState(false); + const [searchedInput, setSearchedInput] = useState(""); // 주소 검색 입력란 저장 const inputRef = useRef(null); @@ -133,6 +134,7 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) { setItems(result.items); setTotal(result.totalCount); + setSearchedInput(keyword); } catch { if (!alive) return; setItems([]); @@ -151,7 +153,7 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) { }; }, [dq, open]); - const showSuggest = open && isInputFocused && q.trim().length >= 1 && suggestions.length > 0; + const showSuggest = open && isInputFocused && q.trim().length >= 1 && suggestions.length > 0 && dq.trim() !== searchedInput; if (!mounted || !open) return null; @@ -234,7 +236,7 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) { className="flex w-full items-center justify-between px-4 py-3 text-left text-[14px] hover:bg-slate-50" onMouseDown={(e) => e.preventDefault()} onClick={() => { - // ✅ 추천 클릭 → 입력값 채우고 드롭다운 닫기 + 포커스 유지 + // 추천 클릭 → 입력값 초기화 드롭다운 닫기 + 포커스 유지 setQ(s); setSuggestions([]); requestAnimationFrame(() => inputRef.current?.focus()); @@ -270,7 +272,9 @@ export default function PostcodeModal({ open, onClose, onSelect }: Props) {