export const NAME_MAX_LENGTH = 100; export const BRAND_MAX_LENGTH = 100; export const OWNER_MAX_LENGTH = 50; export const ADDRESS_DETAIL_MAX_LENGTH = 200; export const MEMO_MAX_LENGTH = 500; export interface MerchantValues { id: string; /** 백엔드에 컬럼이 없어 저장되지 않는다 — 기획 시안 유지를 위해 화면에는 남긴다. */ brandName: string; memo: string; name: string; categoryCode: string; businessNumber: string; ownerName: string; phoneNumber: string; address: string; addressDetail: string; postalCode: string; latitude: string; longitude: string; isVisible: boolean; } export type MerchantFormErrors = Partial< Record<'name' | 'categoryCode' | 'phoneNumber' | 'address' | 'businessNumber', string> >; export type MerchantFormState = | { status: 'idle' } | { status: 'error'; message?: string; errors?: MerchantFormErrors } | { status: 'success' }; export const INITIAL_MERCHANT_FORM_STATE: MerchantFormState = { status: 'idle' }; export const REQUIRED_MESSAGE = '필수입력 사항을 입력하세요.'; /** 사업자등록번호는 선택이지만, 넣었다면 형태를 지킨다(기획 예시 `000-00-00000`). */ const BUSINESS_NUMBER_PATTERN = /^\d{3}-?\d{2}-?\d{5}$/; type ValidationResult = | { ok: true; values: MerchantValues } | { ok: false; errors: MerchantFormErrors }; export function validateMerchant(values: MerchantValues): ValidationResult { const errors: MerchantFormErrors = {}; const name = values.name.trim(); if (!name) { errors.name = REQUIRED_MESSAGE; } else if (name.length > NAME_MAX_LENGTH) { errors.name = `가맹점명은 ${NAME_MAX_LENGTH}자 이내로 입력해 주세요.`; } if (!values.categoryCode.trim()) { errors.categoryCode = REQUIRED_MESSAGE; } const phoneNumber = values.phoneNumber.trim(); if (!phoneNumber) { errors.phoneNumber = REQUIRED_MESSAGE; } else if (!/^[\d-]+$/.test(phoneNumber)) { errors.phoneNumber = '전화번호는 숫자와 -만 입력할 수 있습니다.'; } if (!values.address.trim()) { errors.address = '주소 검색을 진행해 주세요.'; } const businessNumber = values.businessNumber.trim(); if (businessNumber && !BUSINESS_NUMBER_PATTERN.test(businessNumber)) { errors.businessNumber = '사업자등록번호는 000-00-00000 형식으로 입력해 주세요.'; } if (Object.keys(errors).length > 0) { return { ok: false, errors }; } return { ok: true, values: { ...values, name, brandName: values.brandName.trim().slice(0, BRAND_MAX_LENGTH), memo: values.memo.trim().slice(0, MEMO_MAX_LENGTH), ownerName: values.ownerName.trim().slice(0, OWNER_MAX_LENGTH), phoneNumber, businessNumber, address: values.address.trim(), addressDetail: values.addressDetail.trim().slice(0, ADDRESS_DETAIL_MAX_LENGTH), }, }; }