임동욱 임동욱 08-19
merge: hub — 저장 실패 시 입력값 유지 등 타 세션 작업 반영
@abf52a35e72bd88cad2fb2faed2c21db4444d73e
app/(protected)/(basic)/admins/_components/admin-member-create-modal.tsx
--- app/(protected)/(basic)/admins/_components/admin-member-create-modal.tsx
+++ app/(protected)/(basic)/admins/_components/admin-member-create-modal.tsx
@@ -39,6 +39,7 @@
 } from '../_actions';
 import { AdminMemberFormFields } from './admin-member-form-fields';
 import styles from './admin-member-modal.module.scss';
+import { submitFormAction } from '@/app/_hooks/submit-form-action';
 
 interface AdminMemberCreateModalProps {
   onClose: () => void;
@@ -159,7 +160,7 @@
         </FoxButtonGroup>
       }
     >
-      <form ref={formRef} action={formAction} className={styles.formList}>
+      <form ref={formRef} onSubmit={(event) => submitFormAction(event, formAction)} className={styles.formList}>
         <FoxInput
           size="md"
           name="name"
app/(protected)/(basic)/admins/_components/admin-member-edit-modal.tsx
--- app/(protected)/(basic)/admins/_components/admin-member-edit-modal.tsx
+++ app/(protected)/(basic)/admins/_components/admin-member-edit-modal.tsx
@@ -23,6 +23,7 @@
 import { updateAdminMemberAction } from '../_actions';
 import { AdminMemberFormFields } from './admin-member-form-fields';
 import styles from './admin-member-modal.module.scss';
+import { submitFormAction } from '@/app/_hooks/submit-form-action';
 
 interface AdminMemberEditModalProps {
   member: AdminMember;
@@ -100,7 +101,7 @@
         </FoxButtonGroup>
       }
     >
-      <form ref={formRef} action={formAction} className={styles.formList}>
+      <form ref={formRef} onSubmit={(event) => submitFormAction(event, formAction)} className={styles.formList}>
         {/* 수정 대상을 가리키는 유일한 입력. 이름·ID는 읽기 전용이라 아예 제출하지 않는다. */}
         <input type="hidden" name="id" value={member.id} />
 
app/(protected)/(basic)/boards/_components/board-post-form-modal.tsx
--- app/(protected)/(basic)/boards/_components/board-post-form-modal.tsx
+++ app/(protected)/(basic)/boards/_components/board-post-form-modal.tsx
@@ -24,6 +24,7 @@
 import { createBoardPostAction, updateBoardPostAction } from '../_actions';
 import { BoardAttachmentField } from './board-attachment-field';
 import { BoardContentField } from './board-content-field';
+import { submitFormAction } from '@/app/_hooks/submit-form-action';
 
 interface BoardPostFormModalProps {
   boardType: BoardType;
@@ -115,7 +116,7 @@
         </>
       }
     >
-      <form id={FORM_ID} action={formAction} className="flex flex-col gap-4">
+      <form id={FORM_ID} onSubmit={(event) => submitFormAction(event, formAction)} className="flex flex-col gap-4">
         <input type="hidden" name="boardType" value={boardType} />
         {!isCreate && post && <input type="hidden" name="id" value={post.id} />}
 
app/(protected)/(basic)/boards/inquiries/_components/inquiry-answer-modal.tsx
--- app/(protected)/(basic)/boards/inquiries/_components/inquiry-answer-modal.tsx
+++ app/(protected)/(basic)/boards/inquiries/_components/inquiry-answer-modal.tsx
@@ -21,6 +21,7 @@
 import { BoardAttachmentField } from '../../_components/board-attachment-field';
 import { BoardContentField } from '../../_components/board-content-field';
 import { FieldError } from '../../_components/board-post-form-modal';
+import { submitFormAction } from '@/app/_hooks/submit-form-action';
 
 interface InquiryAnswerModalProps {
   post: BoardPost;
@@ -81,7 +82,7 @@
         </>
       }
     >
-      <form id={FORM_ID} action={formAction} className="flex flex-col gap-4">
+      <form id={FORM_ID} onSubmit={(event) => submitFormAction(event, formAction)} className="flex flex-col gap-4">
         <input type="hidden" name="id" value={post.id} />
 
         <Field label="질문유형">
app/(protected)/(basic)/decoration-items/_components/decoration-item-create-modal.tsx
--- app/(protected)/(basic)/decoration-items/_components/decoration-item-create-modal.tsx
+++ app/(protected)/(basic)/decoration-items/_components/decoration-item-create-modal.tsx
@@ -11,6 +11,7 @@
 import { INITIAL_DECORATION_ITEM_FORM_STATE } from '@/lib/domain/decoration-item-form';
 import { createDecorationItemAction } from '../_actions';
 import { DecorationItemFormFields } from './decoration-item-form-fields';
+import { submitFormAction } from '@/app/_hooks/submit-form-action';
 
 interface DecorationItemCreateModalProps {
   /** 등록 버튼을 누른 시점의 활성 탭(유형) — 폼의 유형 기본값으로 쓴다. */
@@ -73,7 +74,7 @@
         </>
       }
     >
-      <form ref={formRef} action={formAction}>
+      <form ref={formRef} onSubmit={(event) => submitFormAction(event, formAction)}>
         <DecorationItemFormFields
           defaultItemType={defaultItemType}
           typeTotalCount={typeTotalCount}
app/(protected)/(basic)/decoration-items/_components/decoration-item-edit-modal.tsx
--- app/(protected)/(basic)/decoration-items/_components/decoration-item-edit-modal.tsx
+++ app/(protected)/(basic)/decoration-items/_components/decoration-item-edit-modal.tsx
@@ -11,6 +11,7 @@
 import { INITIAL_DECORATION_ITEM_FORM_STATE } from '@/lib/domain/decoration-item-form';
 import { updateDecorationItemAction } from '../_actions';
 import { DecorationItemFormFields } from './decoration-item-form-fields';
+import { submitFormAction } from '@/app/_hooks/submit-form-action';
 
 interface DecorationItemEditModalProps {
   item: DecorationItem;
@@ -69,7 +70,7 @@
         </>
       }
     >
-      <form ref={formRef} action={formAction}>
+      <form ref={formRef} onSubmit={(event) => submitFormAction(event, formAction)}>
         <input type="hidden" name="itemSn" value={item.itemSn} />
 
         <DecorationItemFormFields
app/(protected)/(basic)/system/codes/_components/code-detail-modal.tsx
--- app/(protected)/(basic)/system/codes/_components/code-detail-modal.tsx
+++ app/(protected)/(basic)/system/codes/_components/code-detail-modal.tsx
@@ -10,6 +10,7 @@
 import { INITIAL_COMMON_CODE_FORM_STATE } from '@/lib/domain/common-code-form';
 import { createCodeDetailAction, updateCodeDetailAction } from '../_actions';
 import styles from './code-form.module.scss';
+import { submitFormAction } from '@/app/_hooks/submit-form-action';
 
 interface CodeDetailModalProps {
   /** 소속 공통코드 — 좌측에서 고른 값이라 팝업에서는 바꿀 수 없다. */
@@ -76,7 +77,7 @@
         </>
       }
     >
-      <form ref={formRef} action={formAction} className={styles.fields}>
+      <form ref={formRef} onSubmit={(event) => submitFormAction(event, formAction)} className={styles.fields}>
         <input type="hidden" name={isEdit ? 'targetComCd' : 'comCd'} value={comCd} />
         {isEdit && (
           <input
app/(protected)/(basic)/system/codes/_components/code-group-modal.tsx
--- app/(protected)/(basic)/system/codes/_components/code-group-modal.tsx
+++ app/(protected)/(basic)/system/codes/_components/code-group-modal.tsx
@@ -17,6 +17,7 @@
 import { INITIAL_COMMON_CODE_FORM_STATE } from '@/lib/domain/common-code-form';
 import { createCodeGroupAction, updateCodeGroupAction } from '../_actions';
 import styles from './code-form.module.scss';
+import { submitFormAction } from '@/app/_hooks/submit-form-action';
 
 interface CodeGroupModalProps {
   /** 수정 대상. 없으면 등록 팝업이다. */
@@ -80,7 +81,7 @@
         </>
       }
     >
-      <form ref={formRef} action={formAction} className={styles.fields}>
+      <form ref={formRef} onSubmit={(event) => submitFormAction(event, formAction)} className={styles.fields}>
         {/* 수정 대상은 읽기 전용 칸이 아니라 이 값으로 정해진다 — 칸이 위조돼도 대상은 안 바뀐다. */}
         {isEdit && (
           <input type="hidden" name="targetComCd" value={group.comCd} />
app/(public)/login/_components/login-form.tsx
--- app/(public)/login/_components/login-form.tsx
+++ app/(public)/login/_components/login-form.tsx
@@ -6,6 +6,7 @@
 import { FoxEyeIcon, FoxEyeSlashIcon } from '@fox/core/icons';
 import { login, type LoginFormState } from '../_actions';
 import styles from '../login.module.scss';
+import { submitFormAction } from '@/app/_hooks/submit-form-action';
 
 const initialState: LoginFormState = {};
 
@@ -35,7 +36,7 @@
   const passwordId = useId();
 
   return (
-    <form action={formAction} className={styles.fields}>
+    <form onSubmit={(event) => submitFormAction(event, formAction)} className={styles.fields}>
       <FoxInput
         size="lg"
         label="ID"
 
app/_hooks/submit-form-action.ts (added)
+++ app/_hooks/submit-form-action.ts
@@ -0,0 +1,24 @@
+'use client';
+
+import { startTransition, type FormEvent } from 'react';
+
+/**
+ * `<form action={formAction}>` 대신 쓰는 제출 핸들러.
+ *
+ * **React는 폼 액션이 끝나면 성공·실패를 가리지 않고 폼을 리셋한다.** 그래서 검증 오류가 하나만
+ * 나도 사용자가 채운 값이 전부 날아가고, 특히 `type="file"` 입력은 프로그램으로 되채울 수 없어
+ * 다음 제출에서는 "이미지를 등록해 주세요"만 반복된다(꾸미기 아이템 등록에서 실제로 그랬다).
+ *
+ * 액션을 `action` 프롭에 맡기지 않고 transition 안에서 직접 부르면 그 리셋이 일어나지 않는다.
+ * 확인: 같은 액션을 두 방식으로 제출했을 때 `action={...}` 쪽만 입력값이 빈 문자열이 됐다.
+ *
+ * 성공 후 폼을 비워야 하는 화면은 각자 팝업을 닫으므로(다음에 열 때 새 폼이다) 리셋이 필요 없다.
+ */
+export function submitFormAction(
+  event: FormEvent<HTMLFormElement>,
+  formAction: (formData: FormData) => void
+): void {
+  event.preventDefault();
+  const formData = new FormData(event.currentTarget);
+  startTransition(() => formAction(formData));
+}
Add a comment
List