dm: 상세주소 추가 및 기존비번 확인

This commit is contained in:
2026-05-06 18:12:05 +09:00
parent a644e80191
commit a5e50c0db7
2 changed files with 131 additions and 9 deletions
+120 -2
View File
@@ -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<HTMLInputElement | null>(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<Option[]>(() => {
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() {
<Divider />
{editMode ? (
<>
<FormRow label="기존 비밀번호 확인" required>
<div className="max-w-[520px]">
<div className="flex gap-2">
<div className="flex-1">
<TextInput
value={currentPw}
onChange={(value) => {
setCurrentPw(value);
setIsCurrentPwVerified(false);
}}
placeholder="기존 비밀번호를 입력하세요."
type="password"
/>
</div>
<button
type="button"
onClick={checkCurrentPassword}
disabled={isCheckingCurrentPw}
className="h-11 shrink-0 rounded bg-[#0B4EA2] px-4 text-base text-white hover:opacity-90 disabled:bg-neutral-400"
>
{isCheckingCurrentPw ? "확인 중..." : "확인"}
</button>
</div>
<p className={`mt-2 text-sm ${isCurrentPwVerified ? "text-emerald-600" : "text-neutral-500"}`}>
{isCurrentPwVerified ? "기존 비밀번호 확인이 완료되었습니다." : "수정 전 기존 비밀번호 확인이 필요합니다."}
</p>
</div>
</FormRow>
<Divider />
</>
) : null}
<FormRow label="비밀번호" required={!editMode}>
<div className="max-w-[420px]">
<TextInput
+11 -7
View File
@@ -30,15 +30,16 @@ function useDebouncedValue<T>(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<AddrItem[]>([]);
const [suggestions, setSuggestions] = useState<string[]>([]);
const [items, setItems] = useState<AddrItem[]>([]); // 검색 결과
const [suggestions, setSuggestions] = useState<string[]>([]); // 추천 주소 목록
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<HTMLInputElement | null>(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) {
<button
key={`${it.zipNo}-${it.roadAddr}`}
type="button"
onClick={() => onSelect(it)}
onClick={() => {setQ("");
setSuggestions([]);
onSelect(it)}}
className={[
"w-full rounded-lg border p-4 text-left transition",
"border-slate-200 hover:border-sky-300 hover:bg-sky-50/30",