🚀 배포용: PDF 뷰어 개선 및 서적별 UI 데본씽크 스타일 적용
✨ 주요 개선사항: - PDF API 500 에러 수정 (한글 파일명 UTF-8 인코딩 처리) - PDF 뷰어 기능 완전 구현 (PDF.js 통합, 네비게이션, 확대/축소) - 서적별 문서 그룹화 UI 데본씽크 스타일로 개선 - PDF Manager 페이지 서적별 보기 기능 추가 - Alpine.js 로드 순서 최적화로 JavaScript 에러 해결 🎨 UI/UX 개선: - 확장/축소 가능한 아코디언 스타일 서적 목록 - 간결하고 직관적인 데본씽크 스타일 인터페이스 - PDF 상태 표시 (HTML 연결, 서적 분류) - 반응형 디자인 및 부드러운 애니메이션 🔧 기술적 개선: - PDF.js 워커 설정 및 토큰 인증 처리 - 서적별 PDF 자동 그룹화 로직 - Alpine.js 컴포넌트 초기화 최적화
This commit is contained in:
105
frontend/static/js/auth.js
Normal file
105
frontend/static/js/auth.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 인증 관련 Alpine.js 컴포넌트
|
||||
*/
|
||||
|
||||
// 인증 모달 컴포넌트
|
||||
window.authModal = () => ({
|
||||
showLogin: false,
|
||||
loginForm: {
|
||||
email: '',
|
||||
password: ''
|
||||
},
|
||||
loginError: '',
|
||||
loginLoading: false,
|
||||
|
||||
async login() {
|
||||
this.loginLoading = true;
|
||||
this.loginError = '';
|
||||
|
||||
try {
|
||||
console.log('🔐 로그인 시도:', this.loginForm.email);
|
||||
|
||||
// API 클래스의 login 메서드 사용 (이미 토큰 설정과 사용자 정보 가져오기 포함)
|
||||
const result = await window.api.login(this.loginForm.email, this.loginForm.password);
|
||||
|
||||
console.log('✅ 로그인 결과:', result);
|
||||
|
||||
if (result.success) {
|
||||
// refresh_token 저장 (access_token은 API 클래스에서 이미 처리됨)
|
||||
const loginResponse = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(this.loginForm)
|
||||
});
|
||||
const tokenData = await loginResponse.json();
|
||||
localStorage.setItem('refresh_token', tokenData.refresh_token);
|
||||
|
||||
console.log('💾 토큰 저장 완료');
|
||||
|
||||
// 전역 상태 업데이트
|
||||
window.dispatchEvent(new CustomEvent('auth-changed', {
|
||||
detail: { isAuthenticated: true, user: result.user }
|
||||
}));
|
||||
|
||||
// 모달 닫기
|
||||
window.dispatchEvent(new CustomEvent('close-login-modal'));
|
||||
this.loginForm = { email: '', password: '' };
|
||||
|
||||
} else {
|
||||
this.loginError = result.message || '로그인에 실패했습니다';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 로그인 오류:', error);
|
||||
this.loginError = error.message || '로그인에 실패했습니다';
|
||||
} finally {
|
||||
this.loginLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async logout() {
|
||||
try {
|
||||
await window.api.logout();
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
} finally {
|
||||
// 로컬 스토리지 정리
|
||||
localStorage.removeItem('refresh_token');
|
||||
|
||||
// 전역 상태 업데이트
|
||||
window.dispatchEvent(new CustomEvent('auth-changed', {
|
||||
detail: { isAuthenticated: false, user: null }
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 자동 토큰 갱신
|
||||
async function refreshTokenIfNeeded() {
|
||||
const refreshToken = localStorage.getItem('refresh_token');
|
||||
if (!refreshToken || !api.token) return;
|
||||
|
||||
try {
|
||||
// 토큰 만료 확인 (JWT 디코딩)
|
||||
const tokenPayload = JSON.parse(atob(api.token.split('.')[1]));
|
||||
const now = Date.now() / 1000;
|
||||
|
||||
// 토큰이 5분 내에 만료되면 갱신
|
||||
if (tokenPayload.exp - now < 300) {
|
||||
const response = await api.refreshToken(refreshToken);
|
||||
api.setToken(response.access_token);
|
||||
localStorage.setItem('refresh_token', response.refresh_token);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Token refresh failed:', error);
|
||||
// 갱신 실패시 로그아웃
|
||||
window.api.setToken(null);
|
||||
localStorage.removeItem('refresh_token');
|
||||
window.dispatchEvent(new CustomEvent('auth-changed', {
|
||||
detail: { isAuthenticated: false, user: null }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// 5분마다 토큰 갱신 체크
|
||||
setInterval(refreshTokenIfNeeded, 5 * 60 * 1000);
|
||||
Reference in New Issue
Block a user