From 7bae9abe28438e9fbb2e3e851eeeb85c3ab63fa9 Mon Sep 17 00:00:00 2001 From: vibsin9322 Date: Tue, 19 Aug 2025 14:02:13 +0900 Subject: [PATCH] Add guest mode functionality for read-only access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement guest mode for unauthenticated users - Allow file viewing and downloading without login - Hide create/edit/delete functions for guests - Add guest mode banner with login prompt - Add read-only badges for guest accessible files - Include permission checks for all CRUD operations - Add responsive guest mode styling - Support both online (Supabase) and offline (localStorage) modes Features: โ€ข Guest users can view all files and download attachments โ€ข Authentication required for create, edit, delete operations โ€ข Seamless transition between guest and authenticated modes โ€ข User-friendly guest experience with clear login prompts ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .claude/settings.local.json | 16 +++++++ script.js | 90 +++++++++++++++++++++++++++++++++++-- styles.css | 53 ++++++++++++++++++++++ supabase-config.js | 13 ++++-- 4 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 .claude/settings.local.json 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;