Files
Poker/public/assets/key.js
T
agomizi1cmandClaude Opus 5.5 e89d867f85 Add admin role with an admin-only hand range study page
- Optional ADMIN_KEY (6 digits, distinct from ACCESS_KEY): entering it on the
  key screen signs in as admin (role-specific HMAC cookie; member cookies
  stay valid). Logged-in members can open /key?admin to enter it.
- /range (range chart + color/category quiz, ported from poker-hand and
  restyled to the site design) and its files under admin/ are served only to
  admins; everyone else gets 404. Card images cached privately for a day.
- Lobby shows a link to the page for admins only (GET /api/me).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 12:13:14 +09:00

89 lines
2.6 KiB
JavaScript

(function () {
'use strict';
const form = document.getElementById('form');
const box = document.getElementById('digits');
const inputs = [...box.querySelectorAll('input')];
const errorEl = document.getElementById('error');
const submitBtn = document.getElementById('submit');
let busy = false;
// /key?admin … 入室済みの人が管理者キーを入れるための画面
if (new URLSearchParams(location.search).has('admin')) {
document.getElementById('key-title').textContent = '管理者キーを入力';
document.getElementById('key-hint').textContent = '管理者用の6桁の数字';
}
const value = () => inputs.map((i) => i.value).join('');
function fill(digits, from) {
let i = from;
for (const d of digits) {
if (i >= inputs.length) break;
inputs[i++].value = d;
}
inputs[Math.min(i, inputs.length - 1)].focus();
if (value().length === 6) submit();
}
inputs.forEach((input, idx) => {
input.addEventListener('input', () => {
const digits = input.value.replace(/\D/g, '');
input.value = '';
if (digits) fill(digits, idx);
errorEl.textContent = '';
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Backspace' && !input.value && idx > 0) {
inputs[idx - 1].value = '';
inputs[idx - 1].focus();
e.preventDefault();
} else if (e.key === 'ArrowLeft' && idx > 0) {
inputs[idx - 1].focus();
} else if (e.key === 'ArrowRight' && idx < inputs.length - 1) {
inputs[idx + 1].focus();
}
});
input.addEventListener('paste', (e) => {
const digits = (e.clipboardData.getData('text') || '').replace(/\D/g, '');
if (!digits) return;
e.preventDefault();
fill(digits, idx);
});
input.addEventListener('focus', () => input.select());
});
async function submit() {
if (busy) return;
const key = value();
if (key.length !== 6) {
errorEl.textContent = '6桁すべて入力してください。';
return;
}
busy = true;
submitBtn.disabled = true;
try {
await App.api('POST', '/api/auth', { key });
const next = new URLSearchParams(location.search).get('next');
location.replace(App.safeNext(next));
} catch (err) {
errorEl.textContent = err.message;
box.classList.remove('shake');
void box.offsetWidth;
box.classList.add('shake');
inputs.forEach((i) => { i.value = ''; });
inputs[0].focus();
} finally {
busy = false;
submitBtn.disabled = false;
}
}
form.addEventListener('submit', (e) => {
e.preventDefault();
submit();
});
inputs[0].focus();
})();