feat: [card] 카드 등록/수정 폼에 필드별 인라인 에러 표시 추가

- executeServerAction: ActionResult에 fieldErrors 추가, API 422 응답의 error.details 파싱
- CardDetail: 각 인풋 아래에 에러 메시지 표시 + 에러 필드 border 강조
- handleChange: 해당 필드 입력 시 에러 자동 클리어
This commit is contained in:
김보곤
2026-02-21 00:27:28 +09:00
parent 30ca2afca8
commit aa9404a146
2 changed files with 92 additions and 23 deletions

View File

@@ -38,11 +38,15 @@
import { serverFetch } from './fetch-wrapper';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
// ===== 필드별 에러 타입 =====
export type FieldErrors = Record<string, string[]>;
// ===== 공통 반환 타입 =====
export interface ActionResult<T = unknown> {
success: boolean;
data?: T;
error?: string;
fieldErrors?: FieldErrors;
__authError?: boolean;
}
@@ -116,14 +120,26 @@ export async function executeServerAction<TApi = unknown, TResult = TApi>(
// API 실패 응답
if (!response.ok || !result.success) {
let errorMsg = result.message || errorMessage;
// Laravel validation errors: { errors: { field: ['msg1', 'msg2'] } }
if (result.errors && typeof result.errors === 'object') {
const validationErrors = Object.values(result.errors).flat().join(', ');
let fieldErrors: FieldErrors | undefined;
// Laravel validation errors: { error: { code: 422, details: { field: ['msg'] } } }
const details = result.error?.details;
if (details && typeof details === 'object') {
fieldErrors = details as FieldErrors;
const validationErrors = Object.values(fieldErrors).flat().join(', ');
if (validationErrors) errorMsg = validationErrors;
}
// fallback: { errors: { field: ['msg'] } } (Laravel 기본 형식)
else if (result.errors && typeof result.errors === 'object') {
fieldErrors = result.errors as FieldErrors;
const validationErrors = Object.values(fieldErrors).flat().join(', ');
if (validationErrors) errorMsg = validationErrors;
}
return {
success: false,
error: errorMsg,
fieldErrors,
};
}