// ─── 폼 필수 입력 검증 ──────────────────────────────────────────────────────── // 필수 항목이 채워지지 않으면 접수되지 않도록 공통 검증을 제공한다. // validateForm(form, rules) → { ok, errors: {key: 메시지} } // rules: [{ k, label, type }] type: 'text' | 'email' | 'phone' | 'agree' // 라벨에는 를 붙여 필수 표시(*)를 노출한다. const FV = (() => { const msgs = { ko: { required:'필수 입력 항목입니다.', email:'올바른 이메일 형식이 아닙니다.', phone:'올바른 연락처를 입력해 주세요.', agree:'개인정보 수집·이용에 동의해 주세요.' }, en: { required:'This field is required.', email:'Enter a valid email address.', phone:'Enter a valid phone number.', agree:'Please agree to the privacy notice.' }, ja: { required:'必須項目です。', email:'メールアドレスの形式が正しくありません。', phone:'正しい連絡先を入力してください。', agree:'個人情報の取扱いに同意してください。' }, }; const M = (lang, k) => (msgs[lang] || msgs.ko)[k]; const isEmail = v => /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(String(v||'').trim()); // 국내 번호 기준: 숫자 9~11자리 (하이픈·공백·괄호 허용) const isPhone = v => { const d = String(v||'').replace(/[^0-9]/g,''); return d.length >= 9 && d.length <= 11; }; const validateForm = (form, rules, lang='ko') => { const errors = {}; rules.forEach(r => { const v = form[r.k]; if (r.type === 'agree') { if (!v) errors[r.k] = M(lang,'agree'); return; } if (r.type === 'multi') { if (!Array.isArray(v) || !v.length) errors[r.k] = M(lang,'required'); return; } const s = String(v ?? '').trim(); if (!s) { errors[r.k] = M(lang,'required'); return; } if (r.type === 'email' && !isEmail(s)) errors[r.k] = M(lang,'email'); if (r.type === 'phone' && !isPhone(s)) errors[r.k] = M(lang,'phone'); }); return { ok: Object.keys(errors).length === 0, errors }; }; return { validateForm, isEmail, isPhone, M }; })(); // 필수 표시 — 라벨 뒤에 붙인다 const Req = () => ; // 입력칸 아래 오류 메시지 const FieldErr = ({ msg }) => msg ? {msg} : null; Object.assign(window, { validateForm: FV.validateForm, isEmailValid: FV.isEmail, isPhoneValid: FV.isPhone, Req, FieldErr });