feat: 가맹점 저장 실패를 토스트로 알린다
폼 맨 아래 FoxHelperText에 있던 문구를 토스트로 올린다 — 등록 폼이 길어서 스크롤 밖에 있으면 저장이 왜 안 됐는지 보이지 않았다. 칸별 오류는 해당 입력칸에 그대로 둔다. 메시지도 백엔드 사유를 그대로 쓴다(형제 화면의 failureMessage와 같은 방식) — 고정 문구는 "저장하지 못했습니다"밖에 알려 주지 못했다. Co-Authored-By: Claude Opus 5
@659034ff7bcb94d2f6ad087843743a7163b47f3f
--- app/(protected)/(basic)/system/merchants/_actions.ts
+++ app/(protected)/(basic)/system/merchants/_actions.ts
... | ... | @@ -3,6 +3,7 @@ |
| 3 | 3 |
import { unstable_rethrow } from 'next/navigation';
|
| 4 | 4 |
import { revalidatePath } from 'next/cache';
|
| 5 | 5 |
import { verifySession } from '@/lib/auth/dal';
|
| 6 |
+import { BackendRequestError } from '@/lib/http/backend-fetch';
|
|
| 6 | 7 |
import { createMerchant } from '@/lib/data/repositories/merchant-repository';
|
| 7 | 8 |
import {
|
| 8 | 9 |
validateMerchant, |
... | ... | @@ -11,6 +12,10 @@ |
| 11 | 12 |
import { MERCHANTS_PATH } from '@/lib/domain/merchant-query';
|
| 12 | 13 |
|
| 13 | 14 |
const SAVE_FAILED_MESSAGE = '저장하지 못했습니다. 잠시 후 다시 시도해 주세요.'; |
| 15 |
+ |
|
| 16 |
+function failureMessage(error: unknown, fallback: string): string {
|
|
| 17 |
+ return error instanceof BackendRequestError ? error.message : fallback; |
|
| 18 |
+} |
|
| 14 | 19 |
|
| 15 | 20 |
function readString(formData: FormData, key: string): string {
|
| 16 | 21 |
const value = formData.get(key); |
... | ... | @@ -49,7 +54,7 @@ |
| 49 | 54 |
} catch (error) {
|
| 50 | 55 |
// 이슈: 세션이 밀렸을 때 backendFetch가 던지는 redirect를 이 catch가 삼키면 안 된다. |
| 51 | 56 |
unstable_rethrow(error); |
| 52 |
- return { status: 'error', message: SAVE_FAILED_MESSAGE };
|
|
| 57 |
+ return { status: 'error', message: failureMessage(error, SAVE_FAILED_MESSAGE) };
|
|
| 53 | 58 |
} |
| 54 | 59 |
|
| 55 | 60 |
revalidatePath(MERCHANTS_PATH); |
--- app/(protected)/(basic)/system/merchants/_components/merchant-form-modal.tsx
+++ app/(protected)/(basic)/system/merchants/_components/merchant-form-modal.tsx
... | ... | @@ -64,6 +64,13 @@ |
| 64 | 64 |
message: isEdit ? '가맹점을 수정했습니다.' : '가맹점을 등록했습니다.', |
| 65 | 65 |
}); |
| 66 | 66 |
onClose(); |
| 67 |
+ return; |
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ // 칸별 오류는 해당 입력칸에 그대로 두고, 폼 전체에 걸린 사유만 토스트로 알린다 — |
|
| 71 |
+ // 긴 폼이라 맨 아래 문구는 스크롤 밖에 있으면 보이지 않는다. |
|
| 72 |
+ if (state.status === 'error' && state.message) {
|
|
| 73 |
+ showToast({ variant: 'danger', message: state.message });
|
|
| 67 | 74 |
} |
| 68 | 75 |
}, [state, showToast, onClose, isEdit]); |
| 69 | 76 |
|
... | ... | @@ -325,10 +332,6 @@ |
| 325 | 332 |
/> |
| 326 | 333 |
</div> |
| 327 | 334 |
</div> |
| 328 |
- )} |
|
| 329 |
- |
|
| 330 |
- {state.status === 'error' && state.message && (
|
|
| 331 |
- <FoxHelperText type="danger" message={state.message} />
|
|
| 332 | 335 |
)} |
| 333 | 336 |
</form> |
| 334 | 337 |
</FoxModal> |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?