인증 요청 안정화와 월간 메일 한도 적용

This commit is contained in:
2026-06-25 14:05:23 +09:00
parent 35a2976182
commit 5fbd58bd07
20 changed files with 350 additions and 30 deletions

94
script/auth-form.js Normal file
View File

@@ -0,0 +1,94 @@
/**
* 인증 화면 제출 버튼 상태 관리
*/
const UaAuthForm = (() => {
/**
* 버튼 로딩 상태 전환
* @param {HTMLButtonElement} button
* @param {boolean} loading
* @param {string} [loadingText='처리 중...']
*/
function setLoading(button, loading, loadingText = '처리 중...') {
if (!button) return;
if (!button.dataset.idleLabel) {
button.dataset.idleLabel = button.textContent.trim();
}
button.disabled = loading;
button.setAttribute('aria-busy', String(loading));
if (loading) {
button.classList.add('cursor-wait', 'opacity-70');
button.innerHTML = `
<span class="inline-flex items-center justify-center gap-2">
<span class="size-4 animate-spin rounded-full border-2 border-current/30 border-t-current" aria-hidden="true"></span>
<span>${loadingText}</span>
</span>
`;
return;
}
button.classList.remove('cursor-wait', 'opacity-70');
button.textContent = button.dataset.idleLabel;
}
/**
* 캐시된 구 API 클라이언트에 메서드가 없을 때 인증 API 직접 요청
* @param {string} path
* @param {object} body
* @returns {Promise<object>}
*/
async function request(path, body) {
const response = await fetch(`${UaDeckApi.getBaseUrl()}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(data.error || `요청 실패 (${response.status})`);
}
return data;
}
/**
* 메일 전송 버튼 재요청 대기 표시
* @param {HTMLButtonElement} button
* @param {number} seconds
* @param {string} label
*/
function startCooldown(button, seconds, label) {
setLoading(button, false);
let remaining = seconds;
button.disabled = true;
button.classList.add('cursor-wait', 'opacity-70');
button.textContent = `${label} (${remaining}초)`;
const timer = window.setInterval(() => {
remaining -= 1;
if (remaining <= 0) {
window.clearInterval(timer);
button.disabled = false;
button.classList.remove('cursor-wait', 'opacity-70');
button.textContent = button.dataset.idleLabel;
return;
}
button.textContent = `${label} (${remaining}초)`;
}, 1000);
}
/**
* 성공 후 버튼을 완료 상태로 고정
* @param {HTMLButtonElement} button
* @param {string} label
*/
function setComplete(button, label) {
setLoading(button, false);
button.disabled = true;
button.classList.add('opacity-70');
button.textContent = label;
}
return { setLoading, request, startCooldown, setComplete };
})();
window.UaAuthForm = UaAuthForm;