"use client"; import { useEffect, useState, useTransition } from "react"; import { useShallow } from "zustand/react/shallow"; import { CreditCard, CheckCircle2, SearchCheck, ShieldOff, XCircle, FileLock2, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { InlineSpinner } from "@/components/ui/loading-spinner"; import { validateKisProfile } from "@/features/settings/apis/kis-auth.api"; import { getRememberedKisValue, isKisRememberEnabled, setKisRememberEnabled, setRememberedKisValue, } from "@/features/settings/lib/kis-remember-storage"; import { useKisRuntimeStore } from "@/features/settings/store/use-kis-runtime-store"; import { SettingsCard } from "./SettingsCard"; /** * @description 한국투자증권 계좌번호 검증 폼입니다. * @remarks UI 흐름: /settings -> 계좌번호 입력 -> 계좌 확인 버튼 -> validate-profile API -> store 반영 -> 대시보드 반영 * @see app/api/kis/validate-profile/route.ts 계좌번호 검증 서버 라우트 * @see features/settings/store/use-kis-runtime-store.ts 검증 성공값을 전역 상태에 저장합니다. */ export function KisProfileForm() { const { kisAccountNoInput, verifiedCredentials, hasHydrated, isKisVerified, isKisProfileVerified, verifiedAccountNo, setKisAccountNoInput, setVerifiedKisProfile, invalidateKisProfileVerification, } = useKisRuntimeStore( useShallow((state) => ({ kisAccountNoInput: state.kisAccountNoInput, verifiedCredentials: state.verifiedCredentials, hasHydrated: state._hasHydrated, isKisVerified: state.isKisVerified, isKisProfileVerified: state.isKisProfileVerified, verifiedAccountNo: state.verifiedAccountNo, setKisAccountNoInput: state.setKisAccountNoInput, setVerifiedKisProfile: state.setVerifiedKisProfile, invalidateKisProfileVerification: state.invalidateKisProfileVerification, })), ); const [statusMessage, setStatusMessage] = useState(null); const [errorMessage, setErrorMessage] = useState(null); const [isValidating, startValidateTransition] = useTransition(); // [State] 계좌번호 입력값을 브라우저 재접속 후 자동 복원할지 여부 const [rememberAccountNo, setRememberAccountNo] = useState(() => isKisRememberEnabled("accountNo"), ); useEffect(() => { if (!hasHydrated || kisAccountNoInput.trim()) { return; } // [Step 1] 세션 입력값이 비어 있을 때만 저장된 계좌번호를 복원합니다. const rememberedAccountNo = getRememberedKisValue("accountNo"); if (rememberedAccountNo) { setKisAccountNoInput(rememberedAccountNo); } }, [hasHydrated, kisAccountNoInput, setKisAccountNoInput]); useEffect(() => { if (!hasHydrated) { return; } // [Step 2] 계좌 기억하기 체크 상태를 저장/해제합니다. setKisRememberEnabled("accountNo", rememberAccountNo); }, [hasHydrated, rememberAccountNo]); useEffect(() => { if (!hasHydrated || !rememberAccountNo) { return; } // [Step 2] 계좌번호 입력값이 바뀔 때 기억하기가 켜져 있으면 값을 갱신합니다. setRememberedKisValue("accountNo", kisAccountNoInput); }, [hasHydrated, rememberAccountNo, kisAccountNoInput]); /** * @description 계좌번호 인증을 해제하고 입력값을 비웁니다. * @see features/settings/store/use-kis-runtime-store.ts setKisAccountNoInput * @see features/settings/store/use-kis-runtime-store.ts invalidateKisProfileVerification */ function handleDisconnectAccount() { setStatusMessage("계좌 인증을 해제했습니다."); setErrorMessage(null); setKisAccountNoInput(""); invalidateKisProfileVerification(); } function handleValidateProfile() { startValidateTransition(async () => { try { setStatusMessage(null); setErrorMessage(null); if (!verifiedCredentials || !isKisVerified) { throw new Error("먼저 앱키 연결을 완료해 주세요."); } const accountNo = kisAccountNoInput.trim(); if (!accountNo) { throw new Error("계좌번호를 입력해 주세요."); } if (!isValidAccountNo(accountNo)) { throw new Error( "계좌번호 형식이 올바르지 않습니다. 8-2 형식(예: 12345678-01)으로 입력해 주세요.", ); } const result = await validateKisProfile({ ...verifiedCredentials, accountNo, }); setVerifiedKisProfile({ accountNo: result.account.normalizedAccountNo, }); setStatusMessage(result.message); } catch (error) { invalidateKisProfileVerification(); setErrorMessage( error instanceof Error ? error.message : "계좌 확인 중 오류가 발생했습니다.", ); } }); } return ( 인증 완료 ) : undefined } className={ !isKisVerified ? "opacity-60 grayscale pointer-events-none" : undefined } footer={{ actions: (
), status: (
{errorMessage && (

{errorMessage}

)} {statusMessage && (

{statusMessage}

)} {!statusMessage && !errorMessage && !isKisVerified && (

먼저 앱키 연결을 완료해 주세요

)} {!statusMessage && !errorMessage && isKisProfileVerified && (

확인된 계좌: {maskAccountNo(verifiedAccountNo)}

)}
), }} >
{/* ========== ACCOUNT GUIDE ========== */}

계좌번호 형식 안내

8-2 형식으로 입력하세요. 예: 12345678-01

{/* ========== ACCOUNT NO INPUT ========== */}
setKisAccountNoInput(e.target.value)} placeholder="계좌번호 (예: 12345678-01)" className="h-10 border-none bg-transparent px-3 text-sm shadow-none focus-visible:ring-0" autoComplete="off" />
setRememberAccountNo(checked === true) } />
); } /** * @description KIS 계좌번호(8-2) 입력 포맷을 검증합니다. * @param value 사용자 입력 계좌번호 * @returns 형식 유효 여부 * @see features/settings/components/KisProfileForm.tsx handleValidateProfile */ function isValidAccountNo(value: string) { const digits = value.replace(/\D/g, ""); return digits.length === 10; } /** * @description 표시용 계좌번호를 마스킹 처리합니다. * @param value 계좌번호(8-2) * @returns 마스킹 계좌번호 * @see features/settings/components/KisProfileForm.tsx 확인된 값 표시 */ function maskAccountNo(value: string | null) { if (!value) return "-"; const digits = value.replace(/\D/g, ""); if (digits.length !== 10) return "********"; return "********-**"; }