Fix medium priority issues from code review

- Add DEPLOY_TIMING constants for deployment simulation
- Add null checks in switchTab function for consistency
- Add role="radiogroup" and fieldset/legend to wizard steps
- Add role="log" and aria-live="polite" to chat container
- Add bottom border to active pricing region button for better visual distinction

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-20 23:44:13 +09:00
parent 8374cbc19b
commit 7463a2a481
2 changed files with 28 additions and 14 deletions

19
app.js
View File

@@ -58,6 +58,14 @@ const PAYMENT_METHODS = [
{ id: 'yearly', name: '연간 결제', desc: '2개월 무료 (17% 할인)', discount: 17 } { id: 'yearly', name: '연간 결제', desc: '2개월 무료 (17% 할인)', discount: 17 }
]; ];
// 배포 시뮬레이션 타이밍 상수
const DEPLOY_TIMING = {
IMAGE_READY: 1500,
CONTAINER_CREATED: 3000,
NETWORK_CONFIGURED: 4500,
COMPLETE: 6000
};
/** /**
* 가격 포맷팅 (한국 원화) * 가격 포맷팅 (한국 원화)
*/ */
@@ -197,26 +205,26 @@ function anvilApp() {
this.logs.push('✅ 이미지 준비 완료'); this.logs.push('✅ 이미지 준비 완료');
this.logs.push('🔧 컨테이너 인스턴스 생성 중...'); this.logs.push('🔧 컨테이너 인스턴스 생성 중...');
this.deployStep = 2; this.deployStep = 2;
}, 1500); }, DEPLOY_TIMING.IMAGE_READY);
setTimeout(() => { setTimeout(() => {
this.logs.push('✅ 컨테이너 생성 완료'); this.logs.push('✅ 컨테이너 생성 완료');
this.logs.push('🌐 네트워크 및 방화벽 구성 중...'); this.logs.push('🌐 네트워크 및 방화벽 구성 중...');
this.deployStep = 3; this.deployStep = 3;
}, 3000); }, DEPLOY_TIMING.CONTAINER_CREATED);
setTimeout(() => { setTimeout(() => {
const randomIP = Math.floor(Math.random() * 254 + 1); const randomIP = Math.floor(Math.random() * 254 + 1);
this.logs.push(`✅ IP 할당 완료: 45.12.89.${randomIP}`); this.logs.push(`✅ IP 할당 완료: 45.12.89.${randomIP}`);
this.logs.push('⚙️ 시스템 서비스 시작 중...'); this.logs.push('⚙️ 시스템 서비스 시작 중...');
this.deployStep = 4; this.deployStep = 4;
}, 4500); }, DEPLOY_TIMING.NETWORK_CONFIGURED);
setTimeout(() => { setTimeout(() => {
this.launching = false; this.launching = false;
this.deployStep = 5; this.deployStep = 5;
this.logs.push('🎉 서버가 활성화되었습니다!'); this.logs.push('🎉 서버가 활성화되었습니다!');
}, 6000); }, DEPLOY_TIMING.COMPLETE);
}, },
// 런처 열기 // 런처 열기
@@ -275,6 +283,9 @@ function switchTab(tab) {
const panelN8n = document.getElementById('panel-n8n'); const panelN8n = document.getElementById('panel-n8n');
const panelTf = document.getElementById('panel-tf'); const panelTf = document.getElementById('panel-tf');
// Null checks for DOM elements
if (!btnN8n || !btnTf || !panelN8n || !panelTf) return;
if (tab === 'n8n') { if (tab === 'n8n') {
// Update ARIA states for n8n tab // Update ARIA states for n8n tab
btnN8n.setAttribute('aria-selected', 'true'); btnN8n.setAttribute('aria-selected', 'true');

View File

@@ -551,11 +551,11 @@
<!-- Tab Buttons --> <!-- Tab Buttons -->
<div class="flex justify-center mb-8"> <div class="flex justify-center mb-8">
<div class="glass-panel p-1.5 rounded-2xl inline-flex gap-1"> <div class="glass-panel p-1.5 rounded-2xl inline-flex gap-1">
<button @click="region = 'global'" :class="region === 'global' ? 'bg-gradient-to-r from-brand-600 to-brand-500 text-white shadow-lg shadow-brand-500/30' : 'text-slate-400 hover:text-white hover:bg-white/5'" class="px-6 py-3 rounded-xl text-sm font-bold transition-all flex items-center gap-2"> <button @click="region = 'global'" :class="region === 'global' ? 'bg-gradient-to-r from-brand-600 to-brand-500 text-white shadow-lg shadow-brand-500/30 border-b-2 border-b-white/60' : 'text-slate-400 hover:text-white hover:bg-white/5'" class="px-6 py-3 rounded-xl text-sm font-bold transition-all flex items-center gap-2">
<span>🌏 Global</span> <span>🌏 Global</span>
<span class="text-[10px] font-normal opacity-80">(Tokyo/SG/HK)</span> <span class="text-[10px] font-normal opacity-80">(Tokyo/SG/HK)</span>
</button> </button>
<button @click="region = 'seoul'" :class="region === 'seoul' ? 'bg-gradient-to-r from-brand-600 to-brand-500 text-white shadow-lg shadow-brand-500/30' : 'text-slate-400 hover:text-white hover:bg-white/5'" class="px-6 py-3 rounded-xl text-sm font-bold transition-all flex items-center gap-2"> <button @click="region = 'seoul'" :class="region === 'seoul' ? 'bg-gradient-to-r from-brand-600 to-brand-500 text-white shadow-lg shadow-brand-500/30 border-b-2 border-b-white/60' : 'text-slate-400 hover:text-white hover:bg-white/5'" class="px-6 py-3 rounded-xl text-sm font-bold transition-all flex items-center gap-2">
<span>🇰🇷 Seoul</span> <span>🇰🇷 Seoul</span>
<span class="text-[10px] font-normal opacity-80">(Premium)</span> <span class="text-[10px] font-normal opacity-80">(Premium)</span>
</button> </button>
@@ -774,7 +774,7 @@
</div> </div>
<!-- Chat Container --> <!-- Chat Container -->
<div id="chat-container" class="flex-1 overflow-y-auto px-3 sm:px-6 py-3 sm:py-4 space-y-2 sm:space-y-4" x-show="wizardStep < 5"> <div id="chat-container" role="log" aria-live="polite" class="flex-1 overflow-y-auto px-3 sm:px-6 py-3 sm:py-4 space-y-2 sm:space-y-4" x-show="wizardStep < 5">
<!-- Messages --> <!-- Messages -->
<template x-for="(msg, idx) in messages" :key="idx"> <template x-for="(msg, idx) in messages" :key="idx">
<div :class="msg.type === 'bot' ? 'flex justify-start' : 'flex justify-end'" <div :class="msg.type === 'bot' ? 'flex justify-start' : 'flex justify-end'"
@@ -793,7 +793,7 @@
<!-- Current Step Options --> <!-- Current Step Options -->
<div class="pt-2"> <div class="pt-2">
<!-- Step 0: Region Selection --> <!-- Step 0: Region Selection -->
<fieldset x-show="wizardStep === 0" class="space-y-3"> <fieldset x-show="wizardStep === 0" role="radiogroup" aria-label="리전 선택" class="space-y-3">
<legend class="sr-only">리전 선택 (지역 선택)</legend> <legend class="sr-only">리전 선택 (지역 선택)</legend>
<template x-for="r in regions" :key="r.id"> <template x-for="r in regions" :key="r.id">
<button @click="selectRegion(r)" <button @click="selectRegion(r)"
@@ -817,7 +817,8 @@
</fieldset> </fieldset>
<!-- Step 1: Plan Selection --> <!-- Step 1: Plan Selection -->
<div x-show="wizardStep === 1" class="space-y-3"> <fieldset x-show="wizardStep === 1" class="space-y-3">
<legend class="sr-only">플랜 선택 (서버 사양 선택)</legend>
<template x-for="p in plans" :key="p"> <template x-for="p in plans" :key="p">
<button @click="selectPlan(p)" <button @click="selectPlan(p)"
class="w-full flex items-center justify-between px-5 py-4 bg-slate-800/80 hover:bg-slate-700 border border-slate-700/50 hover:border-brand-500/50 rounded-xl transition-all"> class="w-full flex items-center justify-between px-5 py-4 bg-slate-800/80 hover:bg-slate-700 border border-slate-700/50 hover:border-brand-500/50 rounded-xl transition-all">
@@ -835,10 +836,11 @@
</div> </div>
</button> </button>
</template> </template>
</div> </fieldset>
<!-- Step 2: OS Selection --> <!-- Step 2: OS Selection -->
<div x-show="wizardStep === 2" class="space-y-3"> <fieldset x-show="wizardStep === 2" class="space-y-3">
<legend class="sr-only">운영체제 선택</legend>
<template x-for="os in osList" :key="os.id"> <template x-for="os in osList" :key="os.id">
<button @click="selectOS(os)" <button @click="selectOS(os)"
class="w-full flex items-center gap-4 px-5 py-4 bg-slate-800/80 hover:bg-slate-700 border border-slate-700/50 hover:border-purple-500/50 rounded-xl transition-all"> class="w-full flex items-center gap-4 px-5 py-4 bg-slate-800/80 hover:bg-slate-700 border border-slate-700/50 hover:border-purple-500/50 rounded-xl transition-all">
@@ -846,10 +848,11 @@
<span class="font-semibold text-white text-base" x-text="os.name"></span> <span class="font-semibold text-white text-base" x-text="os.name"></span>
</button> </button>
</template> </template>
</div> </fieldset>
<!-- Step 3: Payment Selection --> <!-- Step 3: Payment Selection -->
<div x-show="wizardStep === 3" class="space-y-3"> <fieldset x-show="wizardStep === 3" class="space-y-3">
<legend class="sr-only">결제 방식 선택</legend>
<template x-for="pay in paymentMethods" :key="pay.id"> <template x-for="pay in paymentMethods" :key="pay.id">
<button @click="selectPayment(pay)" <button @click="selectPayment(pay)"
class="w-full flex items-center justify-between px-5 py-4 bg-slate-800/80 hover:bg-slate-700 border border-slate-700/50 hover:border-green-500/50 rounded-xl transition-all"> class="w-full flex items-center justify-between px-5 py-4 bg-slate-800/80 hover:bg-slate-700 border border-slate-700/50 hover:border-green-500/50 rounded-xl transition-all">
@@ -862,7 +865,7 @@
</div> </div>
</button> </button>
</template> </template>
</div> </fieldset>
<!-- Step 4: Confirmation --> <!-- Step 4: Confirmation -->
<div x-show="wizardStep === 4" class="space-y-4"> <div x-show="wizardStep === 4" class="space-y-4">