/** * YS Project - Authentication JavaScript * Handles registration form validation and login form */ (function () { 'use strict'; // ============================================================ // Utility // ============================================================ const $ = (sel) => document.querySelector(sel); let debounceTimers = {}; function debounce(key, fn, delay = 400) { if (debounceTimers[key]) clearTimeout(debounceTimers[key]); debounceTimers[key] = setTimeout(fn, delay); } function setFeedback(el, message, type) { if (!el) return; el.textContent = message; el.className = 'auth-form__feedback'; if (type === 'error') el.classList.add('auth-form__feedback--error'); else if (type === 'success') el.classList.add('auth-form__feedback--success'); } function setInputState(input, state) { if (!input) return; input.classList.remove('auth-form__input--error', 'auth-form__input--success'); if (state === 'error') input.classList.add('auth-form__input--error'); else if (state === 'success') input.classList.add('auth-form__input--success'); } // ============================================================ // Registration Form // ============================================================ const registerForm = $('#registerForm'); if (registerForm) { const fields = { loginId: $('#regLoginId'), nickname: $('#regNickname'), password: $('#regPassword'), passwordConfirm: $('#regPasswordConfirm'), }; const feedbacks = { loginId: $('#loginIdFeedback'), nickname: $('#nicknameFeedback'), password: $('#passwordFeedback'), passwordConfirm: $('#passwordConfirmFeedback'), }; const checkBtns = { loginId: $('#checkLoginIdBtn'), nickname: $('#checkNicknameBtn'), }; const submitBtn = $('#registerSubmitBtn'); const errorBox = $('#registerError'); // Validation state tracking const validState = { loginId: false, loginIdChecked: false, nickname: false, nicknameChecked: false, password: false, passwordConfirm: false, }; function updateSubmitBtn() { const allValid = validState.loginId && validState.loginIdChecked && validState.nickname && validState.nicknameChecked && validState.password && validState.passwordConfirm; submitBtn.disabled = !allValid; } // --- Login ID validation --- function validateLoginId(value) { if (value.length === 0) return { valid: false, message: '' }; if (value.length < 4) return { valid: false, message: '4자 이상 입력해주세요.' }; if (value.length > 20) return { valid: false, message: '20자 이하로 입력해주세요.' }; if (!/^[a-zA-Z0-9_]+$/.test(value)) return { valid: false, message: '영문, 숫자, 언더바(_)만 사용 가능합니다.' }; return { valid: true, message: '중복확인을 해주세요.' }; } fields.loginId.addEventListener('input', function () { const val = this.value.trim(); const result = validateLoginId(val); validState.loginId = result.valid; validState.loginIdChecked = false; checkBtns.loginId.disabled = !result.valid; if (val.length === 0) { setFeedback(feedbacks.loginId, '', ''); setInputState(this, ''); } else if (result.valid) { setFeedback(feedbacks.loginId, result.message, ''); setInputState(this, ''); } else { setFeedback(feedbacks.loginId, result.message, 'error'); setInputState(this, 'error'); } updateSubmitBtn(); }); checkBtns.loginId.addEventListener('click', function () { const val = fields.loginId.value.trim(); if (!validState.loginId) return; checkDuplicate('login_id', val, fields.loginId, feedbacks.loginId, 'loginIdChecked'); }); // --- Nickname validation --- function validateNickname(value) { if (value.length === 0) return { valid: false, message: '' }; if (value.length < 2) return { valid: false, message: '2자 이상 입력해주세요.' }; if (value.length > 12) return { valid: false, message: '12자 이하로 입력해주세요.' }; return { valid: true, message: '중복확인을 해주세요.' }; } fields.nickname.addEventListener('input', function () { const val = this.value.trim(); const result = validateNickname(val); validState.nickname = result.valid; validState.nicknameChecked = false; checkBtns.nickname.disabled = !result.valid; if (val.length === 0) { setFeedback(feedbacks.nickname, '', ''); setInputState(this, ''); } else if (result.valid) { setFeedback(feedbacks.nickname, result.message, ''); setInputState(this, ''); } else { setFeedback(feedbacks.nickname, result.message, 'error'); setInputState(this, 'error'); } updateSubmitBtn(); }); checkBtns.nickname.addEventListener('click', function () { const val = fields.nickname.value.trim(); if (!validState.nickname) return; checkDuplicate('nickname', val, fields.nickname, feedbacks.nickname, 'nicknameChecked'); }); // --- Password validation --- fields.password.addEventListener('input', function () { const val = this.value; if (val.length === 0) { validState.password = false; setFeedback(feedbacks.password, '', ''); setInputState(this, ''); } else if (val.length < 8) { validState.password = false; setFeedback(feedbacks.password, '8자 이상 입력해주세요.', 'error'); setInputState(this, 'error'); } else { validState.password = true; setFeedback(feedbacks.password, '사용 가능한 비밀번호입니다.', 'success'); setInputState(this, 'success'); } // Re-check password confirm checkPasswordConfirm(); updateSubmitBtn(); }); // --- Password confirm --- fields.passwordConfirm.addEventListener('input', function () { checkPasswordConfirm(); updateSubmitBtn(); }); function checkPasswordConfirm() { const pw = fields.password.value; const pwc = fields.passwordConfirm.value; if (pwc.length === 0) { validState.passwordConfirm = false; setFeedback(feedbacks.passwordConfirm, '', ''); setInputState(fields.passwordConfirm, ''); return; } if (pw !== pwc) { validState.passwordConfirm = false; setFeedback(feedbacks.passwordConfirm, '비밀번호가 일치하지 않습니다.', 'error'); setInputState(fields.passwordConfirm, 'error'); } else { validState.passwordConfirm = true; setFeedback(feedbacks.passwordConfirm, '비밀번호가 일치합니다.', 'success'); setInputState(fields.passwordConfirm, 'success'); } } // --- Duplicate check API --- async function checkDuplicate(type, value, inputEl, feedbackEl, stateKey) { try { const params = new URLSearchParams({ type, value }); const resp = await fetch(`/api/check_duplicate.php?${params}`); const data = await resp.json(); if (data.success && data.available) { validState[stateKey] = true; setFeedback(feedbackEl, data.message, 'success'); setInputState(inputEl, 'success'); } else { validState[stateKey] = false; setFeedback(feedbackEl, data.message || data.error, 'error'); setInputState(inputEl, 'error'); } } catch (err) { validState[stateKey] = false; setFeedback(feedbackEl, '확인 중 오류가 발생했습니다.', 'error'); setInputState(inputEl, 'error'); } updateSubmitBtn(); } // --- Form submit --- registerForm.addEventListener('submit', async function (e) { e.preventDefault(); if (submitBtn.disabled) return; // Disable button, show loading submitBtn.disabled = true; submitBtn.classList.add('auth-form__submit--loading'); errorBox.style.display = 'none'; try { const formData = new FormData(registerForm); const resp = await fetch('/api/register.php', { method: 'POST', body: formData, }); const data = await resp.json(); if (data.success) { window.location.href = data.redirect_url || '/'; } else { errorBox.textContent = data.error || '회원가입에 실패했습니다.'; errorBox.style.display = 'block'; submitBtn.disabled = false; submitBtn.classList.remove('auth-form__submit--loading'); } } catch (err) { errorBox.textContent = '서버와 통신 중 오류가 발생했습니다.'; errorBox.style.display = 'block'; submitBtn.disabled = false; submitBtn.classList.remove('auth-form__submit--loading'); } }); } // ============================================================ // Login Form // ============================================================ const loginForm = $('#loginForm'); if (loginForm) { const loginTypeInput = $('#loginType'); const tabBtns = document.querySelectorAll('.auth-tabs__btn'); const submitBtn = $('#loginSubmitBtn'); const errorBox = $('#loginError'); const loginFooter = $('#loginFooter'); // Tab switching tabBtns.forEach(function (btn) { btn.addEventListener('click', function () { tabBtns.forEach(function (b) { b.classList.remove('auth-tabs__btn--active'); }); btn.classList.add('auth-tabs__btn--active'); const type = btn.dataset.loginType; loginTypeInput.value = type; // Show/hide register link based on login type if (type === 'owner') { loginFooter.innerHTML = '
사장님 계정은 관리자에게 문의해주세요.
'; } else { loginFooter.innerHTML = '계정이 없으신가요? 회원가입
'; } // Clear error errorBox.style.display = 'none'; }); }); // Form submit loginForm.addEventListener('submit', async function (e) { e.preventDefault(); const loginId = $('#loginId').value.trim(); const password = $('#loginPassword').value; if (!loginId || !password) { errorBox.textContent = 'ID와 비밀번호를 입력해주세요.'; errorBox.style.display = 'block'; return; } submitBtn.disabled = true; submitBtn.classList.add('auth-form__submit--loading'); errorBox.style.display = 'none'; try { const formData = new FormData(loginForm); const resp = await fetch('/api/login.php', { method: 'POST', body: formData, }); const data = await resp.json(); if (data.success) { window.location.href = data.redirect_url || '/'; } else { errorBox.textContent = data.error || '로그인에 실패했습니다.'; errorBox.style.display = 'block'; submitBtn.disabled = false; submitBtn.classList.remove('auth-form__submit--loading'); } } catch (err) { errorBox.textContent = '서버와 통신 중 오류가 발생했습니다.'; errorBox.style.display = 'block'; submitBtn.disabled = false; submitBtn.classList.remove('auth-form__submit--loading'); } }); } })();