refactor: app.js를 ES6 모듈로 분리

## 변경사항
- app.js (1370줄) → 7개 모듈 (1427줄)
- ES6 import/export 문법 사용
- Alpine.js 호환성 유지 (window 전역 노출)

## 모듈 구조
- js/config.js: 상수/설정 (WIZARD_CONFIG, PRICING_DATA, MOCK_*)
- js/api.js: ApiService
- js/utils.js: formatPrice, switchTab, ping 시뮬레이션
- js/wizard.js: 서버 추천 마법사 로직
- js/pricing.js: 가격표 컴포넌트
- js/dashboard.js: 대시보드 및 텔레그램 연동
- js/app.js: 메인 통합 (모든 모듈 import)

## HTML 변경
- <script type="module" src="js/app.js">로 변경
- 기존 기능 모두 정상 작동

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-23 12:59:54 +09:00
parent 347a5cc598
commit 758266d8cb
21 changed files with 2193 additions and 194 deletions

350
js/dashboard.js Normal file
View File

@@ -0,0 +1,350 @@
/**
* Dashboard Component
* 텔레그램 대시보드 관련 상태 및 메서드
*/
import { MOCK_SERVERS, MOCK_STATS, MOCK_NOTIFICATIONS } from './config.js';
/**
* 대시보드 메서드 생성
*/
export function createDashboardMethods() {
return {
// 대시보드 상태
dashboardMode: false, // true면 대시보드, false면 랜딩
currentView: 'servers', // 'servers' | 'stats' | 'notifications'
// 텔레그램 연동
telegram: {
isAvailable: false, // 텔레그램 환경인지
user: null, // 사용자 정보
initData: null // 검증용 데이터
},
// 웹 로그인 사용자 (텔레그램 로그인 위젯 사용)
webUser: null,
// 현재 로그인된 사용자 (텔레그램 또는 웹)
get currentUser() {
return this.telegram.user || this.webUser;
},
// 서버 목록
servers: [],
loadingServers: false,
// 통계
stats: {
totalCost: 0,
totalServers: 0,
runningServers: 0,
costBreakdown: []
},
// 알림
notifications: [],
unreadCount: 0,
// API 상태
apiLoading: false,
apiError: null,
// 초기화 (텔레그램 연동 + 대시보드)
init() {
if (window.Telegram?.WebApp) {
const tg = window.Telegram.WebApp;
tg.ready();
tg.expand();
this.telegram.isAvailable = true;
this.telegram.user = tg.initDataUnsafe.user || null;
this.telegram.initData = tg.initData;
console.log('[Telegram] Mini App initialized', {
user: this.telegram.user,
platform: tg.platform
});
// 텔레그램 환경이면 대시보드 모드 활성화
this.dashboardMode = true;
this.loadDashboard();
console.log('[Telegram] Dashboard mode activated', {
isAvailable: this.telegram.isAvailable,
hasUser: !!this.telegram.user
});
} else {
console.log('[Telegram] Running in web browser mode');
}
},
// 미니앱 전용 초기화 (/app 페이지용)
initMiniApp() {
const tg = window.Telegram?.WebApp;
// 실제 텔레그램 환경인지 확인 (initData가 있어야 진짜 텔레그램)
const isRealTelegram = tg && tg.initData && tg.initData.length > 0;
if (isRealTelegram) {
tg.ready();
tg.expand();
this.telegram.isAvailable = true;
this.telegram.user = tg.initDataUnsafe.user || null;
this.telegram.initData = tg.initData;
console.log('[MiniApp] Telegram environment detected', {
user: this.telegram.user,
platform: tg.platform
});
// 미니앱은 무조건 대시보드 로드
this.loadDashboard();
} else {
console.log('[MiniApp] Not in Telegram environment');
this.telegram.isAvailable = false;
// 웹 브라우저: localStorage에서 webUser 복원
const savedUser = localStorage.getItem('anvil_web_user');
if (savedUser) {
try {
this.webUser = JSON.parse(savedUser);
console.log('[MiniApp] Web user restored from localStorage:', this.webUser);
this.loadDashboard();
} catch (e) {
console.error('[MiniApp] Failed to parse saved user:', e);
localStorage.removeItem('anvil_web_user');
// 복원 실패시 로그인 위젯 표시
this.loadTelegramLoginWidget();
}
} else {
// 로그인 필요 - 텔레그램 로그인 위젯 로드
this.loadTelegramLoginWidget();
}
}
},
// 텔레그램 로그인 위젯 동적 로드
loadTelegramLoginWidget() {
// Alpine이 DOM을 렌더링할 시간을 주기 위해 약간 지연
setTimeout(() => {
const container = document.getElementById('telegram-login-container');
if (!container) {
console.log('[MiniApp] Login container not found, retrying...');
setTimeout(() => this.loadTelegramLoginWidget(), 100);
return;
}
// 이미 위젯이 있으면 스킵
if (container.querySelector('iframe')) {
console.log('[MiniApp] Login widget already loaded');
return;
}
console.log('[MiniApp] Loading Telegram Login Widget...');
// 텔레그램 위젯 스크립트 동적 생성
const script = document.createElement('script');
script.src = 'https://telegram.org/js/telegram-widget.js?22';
script.setAttribute('data-telegram-login', 'AnvilForgeBot');
script.setAttribute('data-size', 'large');
script.setAttribute('data-radius', '12');
script.setAttribute('data-onauth', 'onTelegramAuth(user)');
script.setAttribute('data-request-access', 'write');
script.async = true;
container.appendChild(script);
console.log('[MiniApp] Telegram Login Widget script added');
}, 50);
},
// 웹 텔레그램 로그인 핸들러
handleWebLogin(user) {
console.log('[MiniApp] Web login received:', user);
// 사용자 정보 저장
this.webUser = {
id: user.id,
first_name: user.first_name,
last_name: user.last_name || '',
username: user.username || '',
photo_url: user.photo_url || '',
auth_date: user.auth_date
};
// localStorage에 저장 (세션 유지)
localStorage.setItem('anvil_web_user', JSON.stringify(this.webUser));
console.log('[MiniApp] Web user logged in:', this.webUser);
// 대시보드 로드
this.loadDashboard();
},
// 로그아웃
logout() {
console.log('[MiniApp] Logging out...');
// webUser 초기화
this.webUser = null;
localStorage.removeItem('anvil_web_user');
// 서버/통계/알림 초기화
this.servers = [];
this.stats = { totalCost: 0, totalServers: 0, runningServers: 0, costBreakdown: [] };
this.notifications = [];
this.unreadCount = 0;
console.log('[MiniApp] Logged out successfully');
},
// 대시보드 초기 로드
async loadDashboard() {
console.log('[Dashboard] Loading dashboard data...');
await Promise.all([
this.fetchServers(),
this.fetchStats(),
this.fetchNotifications()
]);
},
// 서버 목록 조회
async fetchServers() {
this.loadingServers = true;
this.apiError = null;
try {
// TODO: 실제 API 호출로 교체
// const response = await fetch('/api/servers', {
// headers: { 'X-Telegram-Init-Data': this.telegram.initData }
// });
// this.servers = await response.json();
// Mock 데이터 사용
await new Promise(resolve => setTimeout(resolve, 500));
this.servers = [...MOCK_SERVERS];
console.log('[Dashboard] Servers loaded:', this.servers.length);
} catch (error) {
console.error('[Dashboard] Failed to fetch servers:', error);
this.apiError = '서버 목록을 불러오는데 실패했습니다.';
} finally {
this.loadingServers = false;
}
},
// 통계 조회
async fetchStats() {
try {
// TODO: 실제 API 호출로 교체
await new Promise(resolve => setTimeout(resolve, 300));
this.stats = { ...MOCK_STATS };
console.log('[Dashboard] Stats loaded:', this.stats);
} catch (error) {
console.error('[Dashboard] Failed to fetch stats:', error);
}
},
// 알림 조회
async fetchNotifications() {
try {
// TODO: 실제 API 호출로 교체
await new Promise(resolve => setTimeout(resolve, 400));
this.notifications = [...MOCK_NOTIFICATIONS];
this.unreadCount = this.notifications.filter(n => !n.read).length;
console.log('[Dashboard] Notifications loaded:', this.notifications.length);
} catch (error) {
console.error('[Dashboard] Failed to fetch notifications:', error);
}
},
// 서버 시작
async startServer(serverId) {
const server = this.servers.find(s => s.id === serverId);
if (!server) return;
console.log('[Dashboard] Starting server:', serverId);
this.apiLoading = true;
try {
// TODO: 실제 API 호출로 교체
await new Promise(resolve => setTimeout(resolve, 1000));
server.status = 'running';
this.stats.runningServers++;
console.log('[Dashboard] Server started:', serverId);
} catch (error) {
console.error('[Dashboard] Failed to start server:', error);
alert('서버를 시작하는데 실패했습니다.');
} finally {
this.apiLoading = false;
}
},
// 서버 중지
async stopServer(serverId) {
const server = this.servers.find(s => s.id === serverId);
if (!server) return;
console.log('[Dashboard] Stopping server:', serverId);
this.apiLoading = true;
try {
// TODO: 실제 API 호출로 교체
await new Promise(resolve => setTimeout(resolve, 1000));
server.status = 'stopped';
this.stats.runningServers--;
console.log('[Dashboard] Server stopped:', serverId);
} catch (error) {
console.error('[Dashboard] Failed to stop server:', error);
alert('서버를 중지하는데 실패했습니다.');
} finally {
this.apiLoading = false;
}
},
// 서버 삭제
async deleteServer(serverId) {
if (!confirm('정말로 이 서버를 삭제하시겠습니까?')) return;
console.log('[Dashboard] Deleting server:', serverId);
this.apiLoading = true;
try {
// TODO: 실제 API 호출로 교체
await new Promise(resolve => setTimeout(resolve, 800));
const serverIndex = this.servers.findIndex(s => s.id === serverId);
if (serverIndex !== -1) {
const deletedServer = this.servers[serverIndex];
this.servers.splice(serverIndex, 1);
// 통계 업데이트
this.stats.totalServers--;
this.stats.totalCost -= deletedServer.cost;
if (deletedServer.status === 'running') {
this.stats.runningServers--;
}
console.log('[Dashboard] Server deleted:', serverId);
}
} catch (error) {
console.error('[Dashboard] Failed to delete server:', error);
alert('서버를 삭제하는데 실패했습니다.');
} finally {
this.apiLoading = false;
}
},
// 모든 알림 읽음 처리
markAllRead() {
this.notifications.forEach(n => n.read = true);
this.unreadCount = 0;
console.log('[Dashboard] All notifications marked as read');
},
// 뷰 전환
switchView(view) {
this.currentView = view;
console.log('[Dashboard] View switched to:', view);
}
};
}