- 다크모드 지원 추가 (모든 페이지 CSS) - 글래스모피즘 카드 스타일 통일 - 하드코딩 컬러 → CSS 변수 전환 - 접근성 개선: skip nav, aria-label, sr-only, role="dialog" - 파일 구조 정리: 이미지/로고 images/ 폴더로 이동, 미사용 파일 삭제 - 서비스 패키지 8시간 → 6시간으로 변경 - OG 이미지를 전용 mingle-OG.png로 변경 - 컨택트 다크모드 위치 박스 투명 처리 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
306 lines
9.2 KiB
JavaScript
306 lines
9.2 KiB
JavaScript
// ========================================
|
|
// Contact 페이지 전용 JavaScript
|
|
// ========================================
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initContactForm();
|
|
initModal();
|
|
initMap();
|
|
initFormValidation();
|
|
});
|
|
|
|
// 연락처 폼 초기화
|
|
function initContactForm() {
|
|
const form = document.getElementById('contactForm');
|
|
|
|
if (form) {
|
|
form.addEventListener('submit', handleFormSubmit);
|
|
|
|
// 실시간 유효성 검사
|
|
const inputs = form.querySelectorAll('input, select, textarea');
|
|
inputs.forEach(input => {
|
|
input.addEventListener('blur', validateField);
|
|
input.addEventListener('input', clearFieldError);
|
|
});
|
|
|
|
// 전화번호 자동 포맷팅
|
|
const phoneInput = document.getElementById('phone');
|
|
if (phoneInput) {
|
|
phoneInput.addEventListener('input', formatPhoneNumber);
|
|
}
|
|
|
|
// 리셋 버튼 확인
|
|
const resetBtn = form.querySelector('button[type="reset"]');
|
|
if (resetBtn) {
|
|
resetBtn.addEventListener('click', handleFormReset);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 폼 제출 처리
|
|
async function handleFormSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const form = e.target;
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
|
|
// 유효성 검사
|
|
if (!validateForm(form)) {
|
|
showNotification('입력 정보를 확인해 주세요.', 'error');
|
|
return;
|
|
}
|
|
|
|
// 제출 버튼 비활성화
|
|
const originalText = submitBtn.textContent;
|
|
submitBtn.textContent = '전송 중...';
|
|
submitBtn.disabled = true;
|
|
|
|
try {
|
|
// 폼 데이터 수집
|
|
const formData = new FormData(form);
|
|
const data = Object.fromEntries(formData);
|
|
|
|
// 실제 서버 전송 (현재는 시뮬레이션)
|
|
await submitContactForm(data);
|
|
|
|
// 성공 메시지
|
|
showNotification('문의가 성공적으로 전송되었습니다. 빠른 시일 내에 연락드리겠습니다.', 'success');
|
|
form.reset();
|
|
|
|
} catch (error) {
|
|
console.error('Form submission error:', error);
|
|
showNotification('전송 중 오류가 발생했습니다. 다시 시도해 주세요.', 'error');
|
|
} finally {
|
|
// 버튼 복원
|
|
submitBtn.textContent = originalText;
|
|
submitBtn.disabled = false;
|
|
}
|
|
}
|
|
|
|
// 서버 전송 시뮬레이션
|
|
async function submitContactForm(data) {
|
|
// 실제 구현 시 서버 API 호출
|
|
return new Promise((resolve, reject) => {
|
|
setTimeout(() => {
|
|
// 90% 확률로 성공
|
|
if (Math.random() > 0.1) {
|
|
resolve({ success: true, message: '전송 완료' });
|
|
} else {
|
|
reject(new Error('서버 오류'));
|
|
}
|
|
}, 2000);
|
|
});
|
|
}
|
|
|
|
// 폼 리셋 처리
|
|
function handleFormReset(e) {
|
|
const confirmed = confirm('입력한 내용이 모두 삭제됩니다. 계속하시겠습니까?');
|
|
if (!confirmed) {
|
|
e.preventDefault();
|
|
} else {
|
|
// 에러 메시지 제거
|
|
const errorElements = document.querySelectorAll('.field-error');
|
|
errorElements.forEach(el => el.remove());
|
|
|
|
// 필드 에러 스타일 제거
|
|
const fields = document.querySelectorAll('.form-group input, .form-group select, .form-group textarea');
|
|
fields.forEach(field => field.classList.remove('error'));
|
|
}
|
|
}
|
|
|
|
// 폼 유효성 검사
|
|
function validateForm(form) {
|
|
let isValid = true;
|
|
|
|
// 필수 필드 검사
|
|
const requiredFields = form.querySelectorAll('[required]');
|
|
requiredFields.forEach(field => {
|
|
if (!validateField({ target: field })) {
|
|
isValid = false;
|
|
}
|
|
});
|
|
|
|
// 이메일 형식 검사
|
|
const emailField = form.querySelector('#email');
|
|
if (emailField && emailField.value && !isValidEmail(emailField.value)) {
|
|
showFieldError(emailField, '올바른 이메일 형식을 입력해 주세요.');
|
|
isValid = false;
|
|
}
|
|
|
|
// 전화번호 형식 검사
|
|
const phoneField = form.querySelector('#phone');
|
|
if (phoneField && phoneField.value && !isValidPhone(phoneField.value)) {
|
|
showFieldError(phoneField, '올바른 전화번호 형식을 입력해 주세요.');
|
|
isValid = false;
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
// 개별 필드 유효성 검사
|
|
function validateField(e) {
|
|
const field = e.target;
|
|
const value = field.value.trim();
|
|
|
|
// 기존 에러 메시지 제거
|
|
clearFieldError(e);
|
|
|
|
// 필수 필드 검사
|
|
if (field.required && !value) {
|
|
showFieldError(field, '필수 입력 항목입니다.');
|
|
return false;
|
|
}
|
|
|
|
// 이메일 형식 검사
|
|
if (field.type === 'email' && value && !isValidEmail(value)) {
|
|
showFieldError(field, '올바른 이메일 형식을 입력해 주세요.');
|
|
return false;
|
|
}
|
|
|
|
// 전화번호 형식 검사
|
|
if (field.type === 'tel' && value && !isValidPhone(value)) {
|
|
showFieldError(field, '올바른 전화번호 형식을 입력해 주세요.');
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 필드 에러 표시
|
|
function showFieldError(field, message) {
|
|
field.classList.add('error');
|
|
|
|
const errorEl = document.createElement('div');
|
|
errorEl.className = 'field-error';
|
|
errorEl.textContent = message;
|
|
errorEl.style.color = '#ef4444';
|
|
errorEl.style.fontSize = '0.875rem';
|
|
errorEl.style.marginTop = '0.25rem';
|
|
|
|
field.parentNode.appendChild(errorEl);
|
|
}
|
|
|
|
// 필드 에러 제거
|
|
function clearFieldError(e) {
|
|
const field = e.target;
|
|
field.classList.remove('error');
|
|
|
|
const errorEl = field.parentNode.querySelector('.field-error');
|
|
if (errorEl) {
|
|
errorEl.remove();
|
|
}
|
|
}
|
|
|
|
// 이메일 유효성 검사
|
|
function isValidEmail(email) {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return emailRegex.test(email);
|
|
}
|
|
|
|
// 전화번호 유효성 검사
|
|
function isValidPhone(phone) {
|
|
const phoneRegex = /^[0-9-+\s()]+$/;
|
|
return phoneRegex.test(phone) && phone.replace(/[^0-9]/g, '').length >= 10;
|
|
}
|
|
|
|
// 전화번호 자동 포맷팅
|
|
function formatPhoneNumber(e) {
|
|
let value = e.target.value.replace(/[^0-9]/g, '');
|
|
|
|
if (value.length >= 11) {
|
|
// 01X-XXXX-XXXX 형태
|
|
value = value.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
|
|
} else if (value.length >= 7) {
|
|
// 01X-XXX-XXXX 또는 01X-XXXX-XXX 형태
|
|
value = value.replace(/(\d{3})(\d{3,4})(\d{0,4})/, '$1-$2-$3');
|
|
} else if (value.length >= 3) {
|
|
// 01X-XXX 형태
|
|
value = value.replace(/(\d{3})(\d{0,4})/, '$1-$2');
|
|
}
|
|
|
|
e.target.value = value;
|
|
}
|
|
|
|
// common.js의 showNotification 사용
|
|
const showNotification = window.commonUtils?.showNotification || function() {};
|
|
|
|
// 모달 초기화
|
|
function initModal() {
|
|
const modal = document.getElementById('privacyModal');
|
|
const privacyLink = document.querySelector('.privacy-link');
|
|
const closeBtn = modal?.querySelector('.modal-close');
|
|
|
|
if (privacyLink && modal) {
|
|
privacyLink.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
modal.classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
});
|
|
}
|
|
|
|
if (closeBtn && modal) {
|
|
closeBtn.addEventListener('click', function() {
|
|
modal.classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
});
|
|
}
|
|
|
|
if (modal) {
|
|
modal.addEventListener('click', function(e) {
|
|
if (e.target === modal) {
|
|
modal.classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
// ESC 키로 모달 닫기
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape' && modal?.classList.contains('active')) {
|
|
modal.classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 지도 초기화
|
|
function initMap() {
|
|
// HTML에 이미 정적으로 정보가 표시되어 있으므로
|
|
// JavaScript로 덮어쓸 필요가 없음
|
|
// 지도 기능은 외부 링크(네이버 지도, 구글 맵)를 통해 제공
|
|
console.log('Map links are ready');
|
|
}
|
|
|
|
|
|
// 폼 유효성 검사 초기화
|
|
function initFormValidation() {
|
|
// CSS로 에러 스타일 정의
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.form-group input.error,
|
|
.form-group select.error,
|
|
.form-group textarea.error {
|
|
border-color: #ef4444 !important;
|
|
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
|
|
}
|
|
|
|
.field-error {
|
|
animation: fadeIn 0.3s ease;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(-5px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.notification {
|
|
animation: slideIn 0.3s ease;
|
|
}
|
|
|
|
@keyframes slideIn {
|
|
from { transform: translateX(100%); opacity: 0; }
|
|
to { transform: translateX(0); opacity: 1; }
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
} |