From 2c4f62240897c045e3f79a33f9ef9d33071e5318 Mon Sep 17 00:00:00 2001 From: zenn Date: Thu, 25 Jun 2026 17:11:31 +0900 Subject: [PATCH] =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=ED=83=88=ED=87=B4?= =?UTF-8?q?=EC=99=80=20=EB=8D=B1=20=EC=9D=BC=EA=B4=84=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- admin.html | 151 ++++++++++++++++++++++++- api/package.json | 2 +- api/src/index.js | 4 +- api/src/routes/admin.js | 65 +++++++++++ api/src/routes/auth.js | 28 +++++ api/src/services/accountDeletion.js | 33 ++++++ api/src/services/avatarImage.js | 8 +- api/src/services/emailService.js | 4 +- api/test/account-deletion.test.js | 164 ++++++++++++++++++++++++++-- docs/changelog.md | 12 ++ docs/deploy.md | 11 +- docs/history.md | 5 + docs/map.md | 9 +- docs/spec.md | 16 ++- docs/update.md | 11 ++ forgot-password.html | 6 +- i18n/translations.en.json | 5 + i18n/translations.ja.json | 5 + i18n/translations.ko.json | 5 + login.html | 8 +- profile.html | 69 +++++++++++- reset-password.html | 6 +- script/api.js | 21 ++++ signup.html | 6 +- style/output.css | 2 +- verify-email.html | 6 +- 27 files changed, 613 insertions(+), 51 deletions(-) create mode 100644 api/src/services/accountDeletion.js diff --git a/VERSION b/VERSION index 5fcb5fe..eb4ff85 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v4.0.15 +v4.0.16 diff --git a/admin.html b/admin.html index d199b01..a59ed39 100644 --- a/admin.html +++ b/admin.html @@ -3,7 +3,7 @@ - + 관리자 - UNIARE @@ -47,6 +47,13 @@ +
+ + +
@@ -119,6 +126,7 @@
+
@@ -153,7 +161,22 @@
- + + + - - + + - - + + - + @@ -116,6 +116,10 @@ + + @@ -158,7 +162,25 @@ - +
+
+

회원 탈퇴

+

+ 계정과 개인 정보는 삭제되며 저장한 덱은 다른 사용자가 계속 참고할 수 있도록 익명으로 유지됩니다. 이 작업은 복구할 수 없습니다. +

+
+ + +
+ +
+ + +
+
+
+ + @@ -328,6 +350,10 @@ document.getElementById('profile-username').value = user.username || ''; document.getElementById('profile-avatar').src = resolveAvatarSrc(user); document.getElementById('profile-avatar-remove').classList.toggle('hidden', !user.avatarUrl); + document.getElementById('profile-open-delete').classList.toggle( + 'hidden', + user.role === 'admin', + ); } /** @@ -462,6 +488,7 @@ }); const passwordModal = document.getElementById('password-modal'); + const accountDeleteModal = document.getElementById('account-delete-modal'); document.getElementById('profile-open-password').addEventListener('click', () => { document.getElementById('password-current').value = ''; @@ -505,6 +532,44 @@ if (e.target === passwordModal) passwordModal.classList.remove('open'); }); + document.getElementById('profile-open-delete').addEventListener('click', () => { + const confirmationInput = document.getElementById('account-delete-confirmation'); + confirmationInput.value = ''; + document.getElementById('account-delete-error').classList.add('hidden'); + document.getElementById('account-delete-yes').disabled = true; + accountDeleteModal.classList.add('open'); + confirmationInput.focus(); + }); + + document.getElementById('account-delete-confirmation').addEventListener('input', (event) => { + document.getElementById('account-delete-yes').disabled = event.target.value !== 'DELETE'; + }); + + document.getElementById('account-delete-no').addEventListener('click', () => { + accountDeleteModal.classList.remove('open'); + }); + + document.getElementById('account-delete-yes').addEventListener('click', async () => { + const confirmation = document.getElementById('account-delete-confirmation').value; + const errorEl = document.getElementById('account-delete-error'); + const yesButton = document.getElementById('account-delete-yes'); + errorEl.classList.add('hidden'); + yesButton.disabled = true; + try { + await UaDeckApi.deleteMyAccount(confirmation); + UaDeckApi.logout(); + location.href = '/'; + } catch (err) { + errorEl.textContent = err.message || t('accountWithdrawalFailed', '회원 탈퇴에 실패했습니다.'); + errorEl.classList.remove('hidden'); + yesButton.disabled = confirmation !== 'DELETE'; + } + }); + + accountDeleteModal.addEventListener('click', (event) => { + if (event.target === accountDeleteModal) accountDeleteModal.classList.remove('open'); + }); + init(); diff --git a/reset-password.html b/reset-password.html index 057b73a..42c62ea 100644 --- a/reset-password.html +++ b/reset-password.html @@ -3,7 +3,7 @@ - + 비밀번호 재설정 - UNIARE @@ -30,8 +30,8 @@

로그인으로 돌아가기

- - + + - - + + - - + +