대시보드 구현

This commit is contained in:
2026-02-12 14:20:07 +09:00
parent 8f1d75b4d5
commit 434a814246
23 changed files with 1759 additions and 24 deletions

View File

@@ -14,6 +14,7 @@ import {
CheckCircle2,
XCircle,
Lock,
CreditCard,
Sparkles,
Zap,
Activity,
@@ -31,11 +32,13 @@ export function KisAuthForm() {
kisTradingEnvInput,
kisAppKeyInput,
kisAppSecretInput,
kisAccountNoInput,
verifiedCredentials,
isKisVerified,
setKisTradingEnvInput,
setKisAppKeyInput,
setKisAppSecretInput,
setKisAccountNoInput,
setVerifiedKisSession,
invalidateKisVerification,
clearKisRuntimeSession,
@@ -44,11 +47,13 @@ export function KisAuthForm() {
kisTradingEnvInput: state.kisTradingEnvInput,
kisAppKeyInput: state.kisAppKeyInput,
kisAppSecretInput: state.kisAppSecretInput,
kisAccountNoInput: state.kisAccountNoInput,
verifiedCredentials: state.verifiedCredentials,
isKisVerified: state.isKisVerified,
setKisTradingEnvInput: state.setKisTradingEnvInput,
setKisAppKeyInput: state.setKisAppKeyInput,
setKisAppSecretInput: state.setKisAppSecretInput,
setKisAccountNoInput: state.setKisAccountNoInput,
setVerifiedKisSession: state.setVerifiedKisSession,
invalidateKisVerification: state.invalidateKisVerification,
clearKisRuntimeSession: state.clearKisRuntimeSession,
@@ -62,7 +67,7 @@ export function KisAuthForm() {
// 입력 필드 Focus 상태 관리를 위한 State
const [focusedField, setFocusedField] = useState<
"appKey" | "appSecret" | null
"appKey" | "appSecret" | "accountNo" | null
>(null);
function handleValidate() {
@@ -73,16 +78,23 @@ export function KisAuthForm() {
const appKey = kisAppKeyInput.trim();
const appSecret = kisAppSecretInput.trim();
const accountNo = kisAccountNoInput.trim();
if (!appKey || !appSecret) {
throw new Error("App Key와 App Secret을 모두 입력해 주세요.");
}
if (accountNo && !isValidAccountNo(accountNo)) {
throw new Error(
"계좌번호 형식이 올바르지 않습니다. 8-2 형식(예: 12345678-01)으로 입력해 주세요.",
);
}
const credentials = {
appKey,
appSecret,
tradingEnv: kisTradingEnvInput,
accountNo: verifiedCredentials?.accountNo ?? "",
accountNo,
};
const result = await validateKisCredentials(credentials);
@@ -221,6 +233,33 @@ export function KisAuthForm() {
/>
</div>
{/* Account No Input */}
<div
className={cn(
"group/input relative flex items-center overflow-hidden rounded-lg border bg-white transition-all duration-200 dark:bg-zinc-900/30",
focusedField === "accountNo"
? "border-brand-500 ring-1 ring-brand-500"
: "border-zinc-200 hover:border-zinc-300 dark:border-zinc-700 dark:hover:border-zinc-600",
)}
>
<div className="hidden h-9 w-9 items-center justify-center border-r border-zinc-100 bg-zinc-50 text-zinc-400 transition-colors group-focus-within/input:text-brand-500 dark:border-zinc-800 dark:bg-zinc-800/50 dark:text-zinc-500 dark:group-focus-within/input:text-brand-400 sm:flex">
<CreditCard className="h-3.5 w-3.5" />
</div>
<div className="flex h-9 min-w-[70px] items-center justify-center border-r border-zinc-100 bg-zinc-50 px-2 text-[11px] font-semibold text-zinc-500 sm:hidden dark:border-zinc-800 dark:bg-zinc-800/50 dark:text-zinc-500">
</div>
<Input
type="text"
value={kisAccountNoInput}
onChange={(e) => setKisAccountNoInput(e.target.value)}
onFocus={() => setFocusedField("accountNo")}
onBlur={() => setFocusedField(null)}
placeholder="12345678-01"
className="h-9 flex-1 border-none bg-transparent px-3 text-xs text-zinc-900 placeholder:text-zinc-400 focus-visible:ring-0 dark:text-zinc-100 dark:placeholder:text-zinc-600"
autoComplete="off"
/>
</div>
{/* App Secret Input */}
<div
className={cn(
@@ -312,3 +351,14 @@ export function KisAuthForm() {
</div>
);
}
/**
* @description KIS 계좌번호(8-2) 입력 포맷을 검증합니다.
* @param value 사용자 입력 계좌번호
* @returns 형식 유효 여부
* @see features/settings/components/KisAuthForm.tsx handleValidate 인증 전 계좌번호 검사
*/
function isValidAccountNo(value: string) {
const digits = value.replace(/\D/g, "");
return digits.length === 10;
}