diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..359de5b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,16 @@ +{ + "permissions": { + "allow": [ + "Bash(git push:*)", + "Bash(git remote remove:*)", + "Bash(git remote add:*)", + "Bash(git remote set-url:*)", + "WebFetch(domain:ngrok.com)", + "Bash(python:*)", + "WebFetch(domain:developers.cloudflare.com)", + "Bash(git add:*)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/script.js b/script.js index 2aec689..c565c39 100644 --- a/script.js +++ b/script.js @@ -80,6 +80,9 @@ class FileManager { this.setupRealtimeSubscription(); } else { this.updateAuthUI(false); + // 게스트 모드: 공개 파일 로드 + await this.loadPublicFiles(); + this.showGuestMode(); } setupAuthListener((event, session) => { @@ -91,8 +94,8 @@ class FileManager { } else if (event === 'SIGNED_OUT') { this.currentUser = null; this.updateAuthUI(false); - this.files = []; - this.renderFiles(); + this.loadPublicFiles(); + this.showGuestMode(); this.cleanupRealtimeSubscription(); } }); @@ -105,15 +108,19 @@ class FileManager { const authButtons = document.getElementById('authButtons'); const userInfo = document.getElementById('userInfo'); const userEmail = document.getElementById('userEmail'); + const formSection = document.querySelector('.form-section'); if (isAuthenticated && this.currentUser) { authButtons.style.display = 'none'; userInfo.style.display = 'flex'; userEmail.textContent = this.currentUser.email; + formSection.style.display = 'block'; this.updateSyncStatus(); + this.hideGuestMode(); } else { authButtons.style.display = 'flex'; userInfo.style.display = 'none'; + formSection.style.display = 'none'; } } @@ -258,6 +265,53 @@ class FileManager { container.insertBefore(offlineNotice, container.firstChild.nextSibling); } + // 게스트 모드 관련 + showGuestMode() { + this.hideGuestMode(); // 기존 알림 제거 + const container = document.querySelector('.container'); + const guestNotice = document.createElement('div'); + guestNotice.className = 'guest-mode'; + guestNotice.id = 'guestModeNotice'; + guestNotice.innerHTML = ` +
+ 👤 게스트 모드 - 파일 보기 및 다운로드만 가능합니다 + +
+ `; + container.insertBefore(guestNotice, container.firstChild.nextSibling); + } + + hideGuestMode() { + const guestNotice = document.getElementById('guestModeNotice'); + if (guestNotice) { + guestNotice.remove(); + } + } + + // 공개 파일 로드 (게스트용) + async loadPublicFiles() { + if (isSupabaseConfigured()) { + try { + // Supabase에서 모든 파일 로드 (RLS로 공개 파일만 접근 가능) + const data = await SupabaseHelper.getFiles('public'); + this.files = data.map(file => ({ + ...file, + files: file.file_attachments || [], + isReadOnly: true + })); + } catch (error) { + console.error('공개 파일 로딩 오류:', error); + // localStorage 폴백 + this.files = this.loadFiles().map(file => ({ ...file, isReadOnly: true })); + } + } else { + // 오프라인 모드: localStorage의 파일을 읽기 전용으로 로드 + this.files = this.loadFiles().map(file => ({ ...file, isReadOnly: true })); + } + this.renderFiles(); + this.updateEmptyState(); + } + setupOnlineStatusListener() { window.addEventListener('online', () => { this.isOnline = true; @@ -490,6 +544,11 @@ class FileManager { handleSubmit(e) { e.preventDefault(); + if (!this.currentUser) { + this.showNotification('로그인이 필요합니다.', 'error'); + return; + } + const title = document.getElementById('fileTitle').value.trim(); const description = document.getElementById('fileDescription').value.trim(); const category = document.getElementById('fileCategory').value; @@ -637,9 +696,12 @@ class FileManager { ${filesHTML}
- - + ${!file.isReadOnly && this.currentUser ? ` + + + ` : ''} ${file.files.length > 0 ? `` : ''} + ${file.isReadOnly ? `👁️ 읽기 전용` : ''}
`; @@ -652,9 +714,19 @@ class FileManager { } editFile(id) { + if (!this.currentUser) { + this.showNotification('로그인이 필요합니다.', 'error'); + return; + } + const file = this.files.find(f => f.id === id); if (!file) return; + if (file.isReadOnly) { + this.showNotification('읽기 전용 파일은 편집할 수 없습니다.', 'error'); + return; + } + this.currentEditId = id; document.getElementById('editTitle').value = file.title; @@ -697,9 +769,19 @@ class FileManager { } deleteFile(id) { + if (!this.currentUser) { + this.showNotification('로그인이 필요합니다.', 'error'); + return; + } + const file = this.files.find(f => f.id === id); if (!file) return; + if (file.isReadOnly) { + this.showNotification('읽기 전용 파일은 삭제할 수 없습니다.', 'error'); + return; + } + if (confirm(`"${file.title}" 자료를 삭제하시겠습니까?\n이 작업은 되돌릴 수 없습니다.`)) { this.deleteFileFromSupabase(id); } diff --git a/styles.css b/styles.css index 66cf1c0..4208376 100644 --- a/styles.css +++ b/styles.css @@ -120,6 +120,50 @@ header p { color: #856404; } +/* 게스트 모드 스타일 */ +.guest-mode { + background: linear-gradient(135deg, #a8e6cf, #88d8a3); + color: #2d3436; + padding: 15px 20px; + border-radius: 8px; + margin: 20px 0; + border: 2px solid #00b894; + box-shadow: 0 2px 10px rgba(0, 184, 148, 0.2); +} + +.guest-mode-content { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 10px; +} + +.guest-login-btn { + background: #00b894; + color: white; + border: none; + padding: 8px 16px; + border-radius: 5px; + cursor: pointer; + font-weight: 500; + transition: all 0.3s ease; +} + +.guest-login-btn:hover { + background: #00a085; + transform: translateY(-1px); +} + +.read-only-badge { + background: #74b9ff; + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 0.8em; + font-weight: 500; +} + .sync-status { display: inline-flex; align-items: center; @@ -569,4 +613,13 @@ header p { .file-actions button { width: 100%; } + + .guest-mode-content { + flex-direction: column; + text-align: center; + } + + .guest-login-btn { + margin-top: 10px; + } } \ No newline at end of file diff --git a/supabase-config.js b/supabase-config.js index fabf629..69bfc06 100644 --- a/supabase-config.js +++ b/supabase-config.js @@ -100,14 +100,19 @@ const SupabaseHelper = { async getFiles(userId) { if (!supabase) throw new Error('Supabase가 초기화되지 않았습니다.'); - const { data, error } = await supabase + let query = supabase .from('files') .select(` *, file_attachments (*) - `) - .eq('user_id', userId) - .order('created_at', { ascending: false }); + `); + + // 공개 파일 요청이 아닌 경우에만 사용자 ID로 필터링 + if (userId !== 'public') { + query = query.eq('user_id', userId); + } + + const { data, error } = await query.order('created_at', { ascending: false }); if (error) throw error; return data;