From f4c88f83df0abd655cd852f151d0da3ab306d84c Mon Sep 17 00:00:00 2001 From: jheaps Date: Thu, 9 Oct 2025 11:09:31 -0600 Subject: [PATCH 1/4] Fix bugs --- .claude/settings.local.json | 3 +- Media.JoshHeaps.Net/Pages/Index.cshtml | 10 +++---- Media.JoshHeaps.Net/Pages/Index.cshtml.cs | 28 ++++++++++++++----- Media.JoshHeaps.Net/Pages/_ViewImports.cshtml | 4 +++ Media.JoshHeaps.Net/wwwroot/js/gallery.js | 9 ++++-- 5 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 Media.JoshHeaps.Net/Pages/_ViewImports.cshtml diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 77e7aa4..c72243a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -4,7 +4,8 @@ "Bash(dotnet build)", "Bash(dotnet run:*)", "Bash(powershell:*)", - "Bash(dotnet script:*)" + "Bash(dotnet script:*)", + "Bash(taskkill:*)" ], "deny": [], "ask": [] diff --git a/Media.JoshHeaps.Net/Pages/Index.cshtml b/Media.JoshHeaps.Net/Pages/Index.cshtml index ab81d29..72128d8 100644 --- a/Media.JoshHeaps.Net/Pages/Index.cshtml +++ b/Media.JoshHeaps.Net/Pages/Index.cshtml @@ -1,4 +1,4 @@ -@page +@page "{handler?}" @model Media.JoshHeaps.Net.Pages.IndexModel @{ ViewData["Title"] = "Dashboard"; @@ -47,7 +47,7 @@ - + @@ -64,10 +64,10 @@ } diff --git a/Media.JoshHeaps.Net/Pages/Index.cshtml.cs b/Media.JoshHeaps.Net/Pages/Index.cshtml.cs index ea74fb8..ee3ad29 100644 --- a/Media.JoshHeaps.Net/Pages/Index.cshtml.cs +++ b/Media.JoshHeaps.Net/Pages/Index.cshtml.cs @@ -17,6 +17,9 @@ namespace Media.JoshHeaps.Net.Pages [BindProperty] public string? Description { get; set; } + [BindProperty] + public long? MediaId { get; set; } + public string? UploadMessage { get; set; } public bool UploadSuccess { get; set; } @@ -39,6 +42,17 @@ namespace Media.JoshHeaps.Net.Pages RequireAuthentication(); LoadUserSession(); + Dashboard = await userService.GetUserDashboardAsync(UserId); + MediaItems = await _mediaService.GetUserMediaAsync(UserId, 0, 20); + + return Page(); + } + + public async Task OnPostAddAsync() + { + RequireAuthentication(); + LoadUserSession(); + if (UploadedFile == null || UploadedFile.Length == 0) { UploadMessage = "Please select a file to upload."; @@ -69,19 +83,19 @@ namespace Media.JoshHeaps.Net.Pages } } - // Reload data - Dashboard = await userService.GetUserDashboardAsync(UserId); - MediaItems = await _mediaService.GetUserMediaAsync(UserId, 0, 20); - - return Page(); + // Redirect to prevent form resubmission on refresh + return RedirectToPage(); } - public async Task OnPostDeleteAsync(long mediaId) + public async Task OnPostDeleteAsync() { RequireAuthentication(); LoadUserSession(); - var success = await _mediaService.DeleteMediaAsync(mediaId, UserId); + if (MediaId.HasValue) + { + var success = await _mediaService.DeleteMediaAsync(MediaId.Value, UserId); + } return RedirectToPage(); } diff --git a/Media.JoshHeaps.Net/Pages/_ViewImports.cshtml b/Media.JoshHeaps.Net/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..ac7c365 --- /dev/null +++ b/Media.JoshHeaps.Net/Pages/_ViewImports.cshtml @@ -0,0 +1,4 @@ +@using Media.JoshHeaps.Net +@using Media.JoshHeaps.Net.Pages +@namespace Media.JoshHeaps.Net.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/Media.JoshHeaps.Net/wwwroot/js/gallery.js b/Media.JoshHeaps.Net/wwwroot/js/gallery.js index fddbe4c..cd07c27 100644 --- a/Media.JoshHeaps.Net/wwwroot/js/gallery.js +++ b/Media.JoshHeaps.Net/wwwroot/js/gallery.js @@ -89,7 +89,7 @@ function createGalleryItem(media) { const form = document.createElement('form'); form.method = 'post'; - form.action = '/?handler=Delete'; + form.action = '?handler=Delete'; // Relative to current page form.style.display = 'inline'; // Add anti-forgery token @@ -101,7 +101,7 @@ function createGalleryItem(media) { const hiddenInput = document.createElement('input'); hiddenInput.type = 'hidden'; - hiddenInput.name = 'mediaId'; + hiddenInput.name = 'MediaId'; // Match C# property name exactly hiddenInput.value = media.id; form.appendChild(hiddenInput); @@ -110,7 +110,10 @@ function createGalleryItem(media) { deleteBtn.className = 'btn-delete'; deleteBtn.textContent = 'Delete'; deleteBtn.onclick = function(e) { - return confirm('Are you sure you want to delete this image?'); + if (!confirm('Are you sure you want to delete this image?')) { + e.preventDefault(); + return false; + } }; form.appendChild(deleteBtn); From 9269e806b20c599cd38b15c241253d3cd865dbed Mon Sep 17 00:00:00 2001 From: jheaps Date: Thu, 9 Oct 2025 11:21:47 -0600 Subject: [PATCH 2/4] Move file addition to modal --- Media.JoshHeaps.Net/Pages/Index.cshtml | 12 +- Media.JoshHeaps.Net/wwwroot/css/gallery.css | 75 +++++ Media.JoshHeaps.Net/wwwroot/js/gallery.js | 300 +++++++++++--------- 3 files changed, 255 insertions(+), 132 deletions(-) diff --git a/Media.JoshHeaps.Net/Pages/Index.cshtml b/Media.JoshHeaps.Net/Pages/Index.cshtml index 72128d8..c74d66e 100644 --- a/Media.JoshHeaps.Net/Pages/Index.cshtml +++ b/Media.JoshHeaps.Net/Pages/Index.cshtml @@ -29,8 +29,11 @@ -
-

Upload Image

+ + + + @section Scripts { } \ No newline at end of file diff --git a/Media.JoshHeaps.Net/wwwroot/css/gallery.css b/Media.JoshHeaps.Net/wwwroot/css/gallery.css index 0a0c53d..60467ac 100644 --- a/Media.JoshHeaps.Net/wwwroot/css/gallery.css +++ b/Media.JoshHeaps.Net/wwwroot/css/gallery.css @@ -1,3 +1,78 @@ +/* Floating Add Files Button */ +.floating-add-btn { + position: fixed; + bottom: 30px; + right: 30px; + width: 60px; + height: 60px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); + cursor: pointer; + z-index: 1000; + transition: transform 0.2s, box-shadow 0.2s; + display: flex; + align-items: center; + justify-content: center; + font-size: 32px; + line-height: 1; +} + +.floating-add-btn:hover { + transform: scale(1.1); + box-shadow: 0 6px 16px rgba(102, 126, 234, 0.6); +} + +.floating-add-btn:active { + transform: scale(0.95); +} + +.floating-add-btn .btn-icon { + transition: transform 0.3s; +} + +.floating-add-btn.active .btn-icon { + transform: rotate(45deg); +} + +/* Upload Form Styles */ +#upload-form-card { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1001; + max-width: 500px; + width: 90%; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +.upload-form-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.close-upload-form { + background: transparent; + border: none; + font-size: 32px; + color: #999; + cursor: pointer; + padding: 0; + width: 32px; + height: 32px; + line-height: 1; + transition: color 0.2s; +} + +.close-upload-form:hover { + color: #333; +} + /* Gallery Grid Layout */ .gallery-grid { display: grid; diff --git a/Media.JoshHeaps.Net/wwwroot/js/gallery.js b/Media.JoshHeaps.Net/wwwroot/js/gallery.js index cd07c27..b878842 100644 --- a/Media.JoshHeaps.Net/wwwroot/js/gallery.js +++ b/Media.JoshHeaps.Net/wwwroot/js/gallery.js @@ -1,154 +1,194 @@ -// Lazy loading for gallery images using Intersection Observer -let currentOffset = 20; // Initial load was 20 items -let isLoading = false; -let hasMore = true; +// Wait for DOM to be fully loaded +document.addEventListener('DOMContentLoaded', function() { + // Upload form toggle functionality + const addFilesBtn = document.getElementById('add-files-btn'); + const uploadFormCard = document.getElementById('upload-form-card'); + const closeUploadBtn = document.getElementById('close-upload-btn'); -// Create intersection observer for lazy loading -const loadingElement = document.getElementById('loading'); -const galleryElement = document.getElementById('gallery'); + if (addFilesBtn && uploadFormCard) { + addFilesBtn.addEventListener('click', () => { + uploadFormCard.style.display = 'block'; + addFilesBtn.classList.add('active'); + }); -if (loadingElement && galleryElement) { - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting && !isLoading && hasMore) { - loadMoreImages(); + if (closeUploadBtn) { + closeUploadBtn.addEventListener('click', () => { + uploadFormCard.style.display = 'none'; + addFilesBtn.classList.remove('active'); + }); + } + + // Close on Escape key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && uploadFormCard.style.display === 'block') { + uploadFormCard.style.display = 'none'; + addFilesBtn.classList.remove('active'); } }); - }, { - rootMargin: '200px' // Start loading 200px before the element comes into view - }); - observer.observe(loadingElement); -} + // Close when clicking outside the form + document.addEventListener('click', (e) => { + if (uploadFormCard.style.display === 'block' && + !uploadFormCard.contains(e.target) && + !addFilesBtn.contains(e.target)) { + uploadFormCard.style.display = 'none'; + addFilesBtn.classList.remove('active'); + } + }); + } -async function loadMoreImages() { - if (isLoading || !hasMore) return; + // Lazy loading for gallery images using Intersection Observer + let currentOffset = 20; // Initial load was 20 items + let isLoading = false; + let hasMore = true; - isLoading = true; - loadingElement.style.display = 'block'; + // Create intersection observer for lazy loading + const loadingElement = document.getElementById('loading'); + const galleryElement = document.getElementById('gallery'); - try { - const response = await fetch(`/api/media/load?offset=${currentOffset}&limit=20`); - - if (!response.ok) { - throw new Error('Failed to load images'); - } - - const mediaItems = await response.json(); - - if (mediaItems.length === 0) { - hasMore = false; - loadingElement.style.display = 'none'; - return; - } - - // Add new images to gallery - mediaItems.forEach(media => { - const galleryItem = createGalleryItem(media); - galleryElement.appendChild(galleryItem); + if (loadingElement && galleryElement) { + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting && !isLoading && hasMore) { + loadMoreImages(); + } + }); + }, { + rootMargin: '200px' // Start loading 200px before the element comes into view }); - currentOffset += mediaItems.length; - } catch (error) { - console.error('Error loading images:', error); - loadingElement.innerHTML = 'Failed to load more images.'; - } finally { - isLoading = false; - if (hasMore) { - loadingElement.style.display = 'none'; + observer.observe(loadingElement); + } + + async function loadMoreImages() { + if (isLoading || !hasMore) return; + + isLoading = true; + loadingElement.style.display = 'block'; + + try { + const response = await fetch(`/api/media/load?offset=${currentOffset}&limit=20`); + + if (!response.ok) { + throw new Error('Failed to load images'); + } + + const mediaItems = await response.json(); + + if (mediaItems.length === 0) { + hasMore = false; + loadingElement.style.display = 'none'; + return; + } + + // Add new images to gallery + mediaItems.forEach(media => { + const galleryItem = createGalleryItem(media); + galleryElement.appendChild(galleryItem); + }); + + currentOffset += mediaItems.length; + } catch (error) { + console.error('Error loading images:', error); + loadingElement.innerHTML = 'Failed to load more images.'; + } finally { + isLoading = false; + if (hasMore) { + loadingElement.style.display = 'none'; + } } } -} -function createGalleryItem(media) { - const item = document.createElement('div'); - item.className = 'gallery-item'; - item.setAttribute('data-media-id', media.id); + function createGalleryItem(media) { + const item = document.createElement('div'); + item.className = 'gallery-item'; + item.setAttribute('data-media-id', media.id); - const img = document.createElement('img'); - img.src = `/api/media/image/${media.id}`; - img.alt = media.fileName; - img.loading = 'lazy'; - item.appendChild(img); + const img = document.createElement('img'); + img.src = `/api/media/image/${media.id}`; + img.alt = media.fileName; + img.loading = 'lazy'; + item.appendChild(img); - if (media.description) { - const desc = document.createElement('div'); - desc.className = 'gallery-item-description'; - desc.textContent = media.description; - item.appendChild(desc); - } - - const info = document.createElement('div'); - info.className = 'gallery-item-info'; - - const date = document.createElement('span'); - date.className = 'gallery-item-date'; - const createdDate = new Date(media.createdAt); - date.textContent = createdDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); - info.appendChild(date); - - const form = document.createElement('form'); - form.method = 'post'; - form.action = '?handler=Delete'; // Relative to current page - form.style.display = 'inline'; - - // Add anti-forgery token - const tokenInput = document.querySelector('input[name="__RequestVerificationToken"]'); - if (tokenInput) { - const tokenClone = tokenInput.cloneNode(true); - form.appendChild(tokenClone); - } - - const hiddenInput = document.createElement('input'); - hiddenInput.type = 'hidden'; - hiddenInput.name = 'MediaId'; // Match C# property name exactly - hiddenInput.value = media.id; - form.appendChild(hiddenInput); - - const deleteBtn = document.createElement('button'); - deleteBtn.type = 'submit'; - deleteBtn.className = 'btn-delete'; - deleteBtn.textContent = 'Delete'; - deleteBtn.onclick = function(e) { - if (!confirm('Are you sure you want to delete this image?')) { - e.preventDefault(); - return false; + if (media.description) { + const desc = document.createElement('div'); + desc.className = 'gallery-item-description'; + desc.textContent = media.description; + item.appendChild(desc); } - }; - form.appendChild(deleteBtn); - info.appendChild(form); - item.appendChild(info); + const info = document.createElement('div'); + info.className = 'gallery-item-info'; - return item; -} + const date = document.createElement('span'); + date.className = 'gallery-item-date'; + const createdDate = new Date(media.createdAt); + date.textContent = createdDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); + info.appendChild(date); -// Optional: Add image preview on click -galleryElement?.addEventListener('click', (e) => { - if (e.target.tagName === 'IMG') { - const modal = createImageModal(e.target.src); - document.body.appendChild(modal); + const form = document.createElement('form'); + form.method = 'post'; + form.action = '?handler=Delete'; // Relative to current page + form.style.display = 'inline'; + + // Add anti-forgery token + const tokenInput = document.querySelector('input[name="__RequestVerificationToken"]'); + if (tokenInput) { + const tokenClone = tokenInput.cloneNode(true); + form.appendChild(tokenClone); + } + + const hiddenInput = document.createElement('input'); + hiddenInput.type = 'hidden'; + hiddenInput.name = 'MediaId'; // Match C# property name exactly + hiddenInput.value = media.id; + form.appendChild(hiddenInput); + + const deleteBtn = document.createElement('button'); + deleteBtn.type = 'submit'; + deleteBtn.className = 'btn-delete'; + deleteBtn.textContent = 'Delete'; + deleteBtn.onclick = function(e) { + if (!confirm('Are you sure you want to delete this image?')) { + e.preventDefault(); + return false; + } + }; + form.appendChild(deleteBtn); + + info.appendChild(form); + item.appendChild(info); + + return item; } -}); -function createImageModal(imageSrc) { - const modal = document.createElement('div'); - modal.className = 'image-modal'; - modal.innerHTML = ` - - - `; - - modal.addEventListener('click', (e) => { - if (e.target.classList.contains('modal-backdrop') || - e.target.classList.contains('modal-close') || - e.target.classList.contains('image-modal')) { - modal.remove(); + // Optional: Add image preview on click + galleryElement?.addEventListener('click', (e) => { + if (e.target.tagName === 'IMG') { + const modal = createImageModal(e.target.src); + document.body.appendChild(modal); } }); - return modal; -} + function createImageModal(imageSrc) { + const modal = document.createElement('div'); + modal.className = 'image-modal'; + modal.innerHTML = ` + + + `; + + modal.addEventListener('click', (e) => { + if (e.target.classList.contains('modal-backdrop') || + e.target.classList.contains('modal-close') || + e.target.classList.contains('image-modal')) { + modal.remove(); + } + }); + + return modal; + } +}); // End of DOMContentLoaded From f59f90c179349ca79648e988abd74aee138ab7e9 Mon Sep 17 00:00:00 2001 From: jheaps Date: Thu, 9 Oct 2025 11:35:54 -0600 Subject: [PATCH 3/4] Reformatting --- Media.JoshHeaps.Net/Pages/Index.cshtml | 22 ++-- Media.JoshHeaps.Net/appsettings.json | 3 - Media.JoshHeaps.Net/wwwroot/css/Home/page.css | 7 +- Media.JoshHeaps.Net/wwwroot/css/gallery.css | 28 +++++ Media.JoshHeaps.Net/wwwroot/js/upload.js | 106 ++++++++++++++++++ 5 files changed, 151 insertions(+), 15 deletions(-) create mode 100644 Media.JoshHeaps.Net/wwwroot/js/upload.js diff --git a/Media.JoshHeaps.Net/Pages/Index.cshtml b/Media.JoshHeaps.Net/Pages/Index.cshtml index c74d66e..f8c3c59 100644 --- a/Media.JoshHeaps.Net/Pages/Index.cshtml +++ b/Media.JoshHeaps.Net/Pages/Index.cshtml @@ -13,11 +13,6 @@

Welcome back, @Model.Dashboard?.Username!

-

Here's your account overview

-
- -
-

Quick Actions

@if (Model.Dashboard?.EmailVerified == false) { @@ -40,17 +35,23 @@ @Model.UploadMessage
} -
+ @Html.AntiForgeryToken()
- - + +
- +
- + +
@@ -89,4 +90,5 @@ @section Scripts { + } \ No newline at end of file diff --git a/Media.JoshHeaps.Net/appsettings.json b/Media.JoshHeaps.Net/appsettings.json index b2fa4e4..5c719a9 100644 --- a/Media.JoshHeaps.Net/appsettings.json +++ b/Media.JoshHeaps.Net/appsettings.json @@ -19,8 +19,5 @@ "MaxFileSizeMB": 10, "AllowedImageTypes": ["image/jpeg", "image/png", "image/gif", "image/webp"], "UploadPath": "App_Data/media" - }, - "Encryption": { - "Key": "***REMOVED***" } } diff --git a/Media.JoshHeaps.Net/wwwroot/css/Home/page.css b/Media.JoshHeaps.Net/wwwroot/css/Home/page.css index 04e4ca5..19892ce 100644 --- a/Media.JoshHeaps.Net/wwwroot/css/Home/page.css +++ b/Media.JoshHeaps.Net/wwwroot/css/Home/page.css @@ -11,10 +11,13 @@ border-radius: 12px; margin-bottom: 30px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + display: flex; + justify-content: space-between; + align-items: center; } .welcome-section h1 { - margin: 0 0 10px 0; + margin: 0; font-size: 32px; font-weight: 600; } @@ -91,7 +94,7 @@ display: flex; gap: 10px; flex-wrap: wrap; - margin-top: 20px; + align-content: space-evenly; } .btn { diff --git a/Media.JoshHeaps.Net/wwwroot/css/gallery.css b/Media.JoshHeaps.Net/wwwroot/css/gallery.css index 60467ac..6565c11 100644 --- a/Media.JoshHeaps.Net/wwwroot/css/gallery.css +++ b/Media.JoshHeaps.Net/wwwroot/css/gallery.css @@ -49,6 +49,34 @@ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); } +/* Upload Progress Bar */ +#upload-progress { + margin: 15px 0; +} + +.progress-bar { + width: 100%; + height: 30px; + background: #f0f0f0; + border-radius: 15px; + overflow: hidden; + margin-bottom: 10px; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); + transition: width 0.3s ease; + width: 0%; +} + +.progress-text { + text-align: center; + font-size: 14px; + color: #555; + font-weight: 500; +} + .upload-form-header { display: flex; justify-content: space-between; diff --git a/Media.JoshHeaps.Net/wwwroot/js/upload.js b/Media.JoshHeaps.Net/wwwroot/js/upload.js new file mode 100644 index 0000000..710cf37 --- /dev/null +++ b/Media.JoshHeaps.Net/wwwroot/js/upload.js @@ -0,0 +1,106 @@ +// Handle multiple file uploads sequentially +document.addEventListener('DOMContentLoaded', function() { + const uploadForm = document.getElementById('upload-form'); + const fileInput = document.getElementById('UploadedFile'); + const descriptionInput = document.getElementById('Description'); + const uploadBtn = document.getElementById('upload-btn'); + const progressContainer = document.getElementById('upload-progress'); + const progressFill = document.getElementById('progress-fill'); + const progressText = document.getElementById('progress-text'); + const galleryElement = document.getElementById('gallery'); + + if (!uploadForm) return; + + uploadForm.addEventListener('submit', async function(e) { + e.preventDefault(); + + const files = fileInput.files; + if (!files || files.length === 0) { + alert('Please select at least one file'); + return; + } + + // Validate file sizes + const maxSize = 10 * 1024 * 1024; // 10MB + for (let file of files) { + if (file.size > maxSize) { + alert(`File "${file.name}" exceeds 10MB limit`); + return; + } + } + + // Disable form during upload + uploadBtn.disabled = true; + fileInput.disabled = true; + descriptionInput.disabled = true; + progressContainer.style.display = 'block'; + + const description = descriptionInput.value; + let successCount = 0; + let failCount = 0; + + // Upload files sequentially + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const progress = Math.round(((i + 1) / files.length) * 100); + + progressFill.style.width = `${progress}%`; + progressText.textContent = `Uploading ${i + 1} of ${files.length}: ${file.name}`; + + try { + const success = await uploadSingleFile(file, description); + if (success) { + successCount++; + } else { + failCount++; + } + } catch (error) { + console.error(`Failed to upload ${file.name}:`, error); + failCount++; + } + } + + // Reset form + uploadBtn.disabled = false; + fileInput.disabled = false; + descriptionInput.disabled = false; + progressContainer.style.display = 'none'; + progressFill.style.width = '0%'; + + // Show results + if (successCount > 0 && failCount === 0) { } + else if (successCount > 0 && failCount > 0) { + alert(`Uploaded ${successCount} image(s), but ${failCount} failed.`); + } else { + alert(`Failed to upload all ${failCount} image(s).`); + } + + // Reset form and reload page + uploadForm.reset(); + window.location.reload(); + }); + + async function uploadSingleFile(file, description) { + const formData = new FormData(); + formData.append('UploadedFile', file); + if (description) { + formData.append('Description', description); + } + + // Get anti-forgery token + const token = document.querySelector('input[name="__RequestVerificationToken"]').value; + formData.append('__RequestVerificationToken', token); + + try { + const response = await fetch('/?handler=Add', { + method: 'POST', + body: formData + }); + + return response.ok; + } catch (error) { + console.error('Upload error:', error); + return false; + } + } +}); From 8bf0667683148a8e4df60325d53acb695292550a Mon Sep 17 00:00:00 2001 From: jheaps Date: Thu, 9 Oct 2025 13:23:44 -0600 Subject: [PATCH 4/4] Enhance user experience --- Media.JoshHeaps.Net/Pages/Index.cshtml | 1 + Media.JoshHeaps.Net/Pages/Profile.cshtml | 180 +++++++++++++++++ Media.JoshHeaps.Net/Pages/Profile.cshtml.cs | 22 +++ .../Pages/Shared/_Layout.cshtml | 2 + Media.JoshHeaps.Net/wwwroot/css/Home/page.css | 86 ++++---- Media.JoshHeaps.Net/wwwroot/css/auth.css | 112 +++++------ Media.JoshHeaps.Net/wwwroot/css/gallery.css | 184 +++++++++++------- Media.JoshHeaps.Net/wwwroot/css/site.css | 81 ++++++++ Media.JoshHeaps.Net/wwwroot/js/gallery.js | 10 +- Media.JoshHeaps.Net/wwwroot/js/theme.js | 38 ++++ 10 files changed, 557 insertions(+), 159 deletions(-) create mode 100644 Media.JoshHeaps.Net/Pages/Profile.cshtml create mode 100644 Media.JoshHeaps.Net/Pages/Profile.cshtml.cs create mode 100644 Media.JoshHeaps.Net/wwwroot/js/theme.js diff --git a/Media.JoshHeaps.Net/Pages/Index.cshtml b/Media.JoshHeaps.Net/Pages/Index.cshtml index f8c3c59..670cd30 100644 --- a/Media.JoshHeaps.Net/Pages/Index.cshtml +++ b/Media.JoshHeaps.Net/Pages/Index.cshtml @@ -20,6 +20,7 @@ Verify Email } + Edit Profile Logout
diff --git a/Media.JoshHeaps.Net/Pages/Profile.cshtml b/Media.JoshHeaps.Net/Pages/Profile.cshtml new file mode 100644 index 0000000..18af864 --- /dev/null +++ b/Media.JoshHeaps.Net/Pages/Profile.cshtml @@ -0,0 +1,180 @@ +@page +@model Media.JoshHeaps.Net.Pages.ProfileModel +@{ + ViewData["Title"] = "Profile Settings"; + Layout = "_Layout"; +} + +@section Styles { + + +} + +
+ ← Back to Dashboard + +
+

Profile Information

+
+ +
@Model.Dashboard?.Username
+
+
+ +
@Model.Dashboard?.Email
+
+
+ +
+ @if (Model.Dashboard?.EmailVerified == true) + { + Verified + } + else + { + Unverified + } +
+
+
+ +
+

Appearance

+
+
+ Dark Mode + Enable dark theme across the application +
+ +
+
+
diff --git a/Media.JoshHeaps.Net/Pages/Profile.cshtml.cs b/Media.JoshHeaps.Net/Pages/Profile.cshtml.cs new file mode 100644 index 0000000..c46ec25 --- /dev/null +++ b/Media.JoshHeaps.Net/Pages/Profile.cshtml.cs @@ -0,0 +1,22 @@ +using Media.JoshHeaps.Net.Models; +using Media.JoshHeaps.Net.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Media.JoshHeaps.Net.Pages +{ + public class ProfileModel(UserService userService) : AuthenticatedPageModel + { + public UserDashboard? Dashboard { get; set; } + + public async Task OnGetAsync() + { + RequireAuthentication(); + LoadUserSession(); + + // Load user dashboard data + Dashboard = await userService.GetUserDashboardAsync(UserId); + + return Page(); + } + } +} diff --git a/Media.JoshHeaps.Net/Pages/Shared/_Layout.cshtml b/Media.JoshHeaps.Net/Pages/Shared/_Layout.cshtml index dff923b..b8b8a2a 100644 --- a/Media.JoshHeaps.Net/Pages/Shared/_Layout.cshtml +++ b/Media.JoshHeaps.Net/Pages/Shared/_Layout.cshtml @@ -9,6 +9,8 @@ @ViewData["Title"] + + @await RenderSectionAsync("Styles", required: false) @await RenderSectionAsync("Scripts", required: false) diff --git a/Media.JoshHeaps.Net/wwwroot/css/Home/page.css b/Media.JoshHeaps.Net/wwwroot/css/Home/page.css index 19892ce..7ce6d83 100644 --- a/Media.JoshHeaps.Net/wwwroot/css/Home/page.css +++ b/Media.JoshHeaps.Net/wwwroot/css/Home/page.css @@ -5,12 +5,12 @@ } .welcome-section { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 40px; - border-radius: 12px; - margin-bottom: 30px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + background: var(--bg-tertiary); + border: 1px solid var(--border-primary); + color: var(--text-primary); + padding: 32px 40px; + border-radius: 8px; + margin-bottom: 32px; display: flex; justify-content: space-between; align-items: center; @@ -18,13 +18,14 @@ .welcome-section h1 { margin: 0; - font-size: 32px; + font-size: 28px; font-weight: 600; + letter-spacing: -0.5px; } .welcome-section p { margin: 0; - opacity: 0.9; + opacity: 0.8; font-size: 16px; } @@ -36,26 +37,31 @@ } .card { - background: white; - border-radius: 12px; - padding: 30px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + background: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: 8px; + padding: 24px; + transition: border-color 0.2s ease; +} + +.card:hover { + border-color: var(--border-secondary); } .card h2 { margin: 0 0 20px 0; - font-size: 20px; + font-size: 18px; font-weight: 600; - color: #333; - border-bottom: 2px solid #667eea; - padding-bottom: 10px; + color: var(--text-primary); + border-bottom: 1px solid var(--border-primary); + padding-bottom: 12px; } .info-row { display: flex; justify-content: space-between; padding: 12px 0; - border-bottom: 1px solid #f0f0f0; + border-bottom: 1px solid var(--border-secondary); } .info-row:last-child { @@ -64,30 +70,30 @@ .info-label { font-weight: 500; - color: #666; + color: var(--text-secondary); } .info-value { - color: #333; + color: var(--text-primary); font-weight: 500; } .status-badge { display: inline-block; padding: 4px 12px; - border-radius: 12px; + border-radius: 6px; font-size: 12px; font-weight: 600; } .status-verified { - background: #d4edda; - color: #155724; + background: rgba(63, 185, 80, 0.15); + color: var(--success); } .status-unverified { - background: #fff3cd; - color: #856404; + background: rgba(187, 128, 9, 0.15); + color: #d29922; } .quick-actions { @@ -98,44 +104,52 @@ } .btn { - padding: 10px 20px; + padding: 8px 16px; border-radius: 6px; text-decoration: none; font-weight: 500; + font-size: 14px; display: inline-block; - transition: all 0.2s; + transition: all 0.2s ease; + border: 1px solid transparent; } .btn-primary { - background: #667eea; - color: white; + background: var(--accent-primary); + color: var(--bg-primary); + border-color: var(--accent-primary); } .btn-primary:hover { - background: #5568d3; + background: var(--accent-hover); + border-color: var(--accent-hover); } .btn-secondary { - background: #6c757d; - color: white; + background: var(--bg-tertiary); + color: var(--text-primary); + border-color: var(--border-primary); } .btn-secondary:hover { - background: #5a6268; + background: var(--bg-hover); + border-color: var(--border-secondary); } .btn-danger { - background: #dc3545; - color: white; + background: transparent; + color: var(--danger); + border-color: var(--danger); } .btn-danger:hover { - background: #c82333; + background: var(--danger); + color: var(--text-primary); } .empty-state { text-align: center; - color: #999; + color: var(--text-secondary); padding: 20px; font-style: italic; } diff --git a/Media.JoshHeaps.Net/wwwroot/css/auth.css b/Media.JoshHeaps.Net/wwwroot/css/auth.css index 10e5a9c..e7c7115 100644 --- a/Media.JoshHeaps.Net/wwwroot/css/auth.css +++ b/Media.JoshHeaps.Net/wwwroot/css/auth.css @@ -1,29 +1,19 @@ /* Authentication Pages Styling */ -:root { - --primary-color: #007bff; - --primary-hover: #0056b3; - --danger-color: #dc3545; - --success-color: #28a745; - --warning-color: #ffc107; - --border-color: #dee2e6; - --input-focus: #80bdff; - --text-muted: #6c757d; -} - .auth-container { min-height: 100vh; display: flex; align-items: center; justify-content: center; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: var(--bg-primary); padding: 20px; } .auth-card { - background: white; - border-radius: 12px; - box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); + background: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: 8px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); padding: 40px; width: 100%; max-width: 450px; @@ -31,18 +21,19 @@ .auth-header { text-align: center; - margin-bottom: 30px; + margin-bottom: 32px; } .auth-header h1 { font-size: 28px; font-weight: 600; - color: #333; - margin-bottom: 10px; + color: var(--text-primary); + margin-bottom: 8px; + letter-spacing: -0.5px; } .auth-header p { - color: var(--text-muted); + color: var(--text-secondary); font-size: 14px; } @@ -52,7 +43,7 @@ .form-label { font-weight: 500; - color: #333; + color: var(--text-primary); margin-bottom: 8px; display: block; font-size: 14px; @@ -61,28 +52,30 @@ .form-control { width: 100%; padding: 12px 16px; - border: 1px solid var(--border-color); + border: 1px solid var(--border-primary); border-radius: 6px; font-size: 14px; - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + background: var(--bg-tertiary); + color: var(--text-primary); + transition: border-color 0.2s ease, box-shadow 0.2s ease; } .form-control:focus { outline: none; - border-color: var(--input-focus); - box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); + border-color: var(--accent-primary); + box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.15); } .form-control.is-invalid { - border-color: var(--danger-color); + border-color: var(--danger); } .form-control.is-invalid:focus { - box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1); + box-shadow: 0 0 0 3px rgba(248, 81, 73, 0.15); } .invalid-feedback { - color: var(--danger-color); + color: var(--danger); font-size: 13px; margin-top: 5px; display: none; @@ -103,20 +96,21 @@ transform: translateY(-50%); background: none; border: none; - color: var(--text-muted); + color: var(--text-secondary); cursor: pointer; padding: 4px 8px; font-size: 14px; + transition: color 0.2s ease; } .password-toggle:hover { - color: #333; + color: var(--text-primary); } .password-strength { margin-top: 8px; height: 4px; - background: var(--border-color); + background: var(--bg-tertiary); border-radius: 2px; overflow: hidden; display: none; @@ -133,17 +127,17 @@ } .password-strength-bar.weak { - background-color: var(--danger-color); + background-color: var(--danger); width: 33%; } .password-strength-bar.medium { - background-color: var(--warning-color); + background-color: #d29922; width: 66%; } .password-strength-bar.strong { - background-color: var(--success-color); + background-color: var(--success); width: 100%; } @@ -151,6 +145,7 @@ font-size: 12px; margin-top: 4px; display: none; + color: var(--text-secondary); } .password-strength-text.active { @@ -167,11 +162,13 @@ margin-right: 8px; width: 16px; height: 16px; + accent-color: var(--accent-primary); + cursor: pointer; } .checkbox-wrapper label { font-size: 14px; - color: #333; + color: var(--text-primary); margin: 0; cursor: pointer; } @@ -179,22 +176,25 @@ .btn-primary { width: 100%; padding: 12px; - background: var(--primary-color); - color: white; - border: none; + background: var(--accent-primary); + color: var(--bg-primary); + border: 1px solid var(--accent-primary); border-radius: 6px; - font-size: 16px; + font-size: 15px; font-weight: 500; cursor: pointer; - transition: background-color 0.15s ease-in-out; + transition: all 0.2s ease; } .btn-primary:hover { - background: var(--primary-hover); + background: var(--accent-hover); + border-color: var(--accent-hover); } .btn-primary:disabled { - background: var(--text-muted); + background: var(--bg-tertiary); + border-color: var(--border-primary); + color: var(--text-secondary); cursor: not-allowed; } @@ -202,22 +202,24 @@ text-align: center; margin-top: 24px; padding-top: 24px; - border-top: 1px solid var(--border-color); + border-top: 1px solid var(--border-primary); } .auth-footer p { - color: var(--text-muted); + color: var(--text-secondary); font-size: 14px; margin: 0; } .auth-footer a { - color: var(--primary-color); + color: var(--accent-primary); text-decoration: none; font-weight: 500; + transition: color 0.2s ease; } .auth-footer a:hover { + color: var(--accent-hover); text-decoration: underline; } @@ -229,29 +231,29 @@ } .alert-danger { - background-color: #f8d7da; - color: #721c24; - border: 1px solid #f5c6cb; + background-color: rgba(248, 81, 73, 0.15); + color: var(--danger); + border: 1px solid rgba(248, 81, 73, 0.3); } .alert-success { - background-color: #d4edda; - color: #155724; - border: 1px solid #c3e6cb; + background-color: rgba(63, 185, 80, 0.15); + color: var(--success); + border: 1px solid rgba(63, 185, 80, 0.3); } .alert-warning { - background-color: #fff3cd; - color: #856404; - border: 1px solid #ffeaa7; + background-color: rgba(187, 128, 9, 0.15); + color: #d29922; + border: 1px solid rgba(187, 128, 9, 0.3); } .spinner { display: inline-block; width: 16px; height: 16px; - border: 2px solid rgba(255, 255, 255, 0.3); - border-top-color: white; + border: 2px solid rgba(88, 166, 255, 0.3); + border-top-color: var(--accent-primary); border-radius: 50%; animation: spin 0.6s linear infinite; margin-right: 8px; diff --git a/Media.JoshHeaps.Net/wwwroot/css/gallery.css b/Media.JoshHeaps.Net/wwwroot/css/gallery.css index 6565c11..7d93f75 100644 --- a/Media.JoshHeaps.Net/wwwroot/css/gallery.css +++ b/Media.JoshHeaps.Net/wwwroot/css/gallery.css @@ -3,34 +3,37 @@ position: fixed; bottom: 30px; right: 30px; - width: 60px; - height: 60px; + width: 56px; + height: 56px; border-radius: 50%; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border: none; - box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); + background: var(--accent-primary); + color: var(--bg-primary); + border: 1px solid var(--accent-primary); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); cursor: pointer; z-index: 1000; - transition: transform 0.2s, box-shadow 0.2s; + transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; - font-size: 32px; + font-size: 28px; line-height: 1; + font-weight: 300; } .floating-add-btn:hover { - transform: scale(1.1); - box-shadow: 0 6px 16px rgba(102, 126, 234, 0.6); + transform: scale(1.05); + background: var(--accent-hover); + border-color: var(--accent-hover); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4); } .floating-add-btn:active { - transform: scale(0.95); + transform: scale(0.98); } .floating-add-btn .btn-icon { - transition: transform 0.3s; + transition: transform 0.3s ease; } .floating-add-btn.active .btn-icon { @@ -46,7 +49,7 @@ z-index: 1001; max-width: 500px; width: 90%; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); } /* Upload Progress Bar */ @@ -56,24 +59,24 @@ .progress-bar { width: 100%; - height: 30px; - background: #f0f0f0; - border-radius: 15px; + height: 8px; + background: var(--bg-tertiary); + border-radius: 4px; overflow: hidden; margin-bottom: 10px; } .progress-fill { height: 100%; - background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); + background: var(--accent-primary); transition: width 0.3s ease; width: 0%; } .progress-text { text-align: center; - font-size: 14px; - color: #555; + font-size: 13px; + color: var(--text-secondary); font-weight: 500; } @@ -87,40 +90,40 @@ .close-upload-form { background: transparent; border: none; - font-size: 32px; - color: #999; + font-size: 28px; + color: var(--text-secondary); cursor: pointer; padding: 0; width: 32px; height: 32px; line-height: 1; - transition: color 0.2s; + transition: color 0.2s ease; } .close-upload-form:hover { - color: #333; + color: var(--text-primary); } /* Gallery Grid Layout */ .gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 20px; + gap: 16px; padding: 20px 0; } .gallery-item { position: relative; - background: #fff; + background: var(--bg-secondary); + border: 1px solid var(--border-primary); border-radius: 8px; overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - transition: transform 0.2s, box-shadow 0.2s; + transition: all 0.2s ease; } .gallery-item:hover { - transform: translateY(-4px); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + transform: translateY(-2px); + border-color: var(--border-secondary); } .gallery-item img { @@ -129,91 +132,137 @@ object-fit: cover; display: block; cursor: pointer; + transition: opacity 0.2s ease; +} + +.gallery-item img:hover { + opacity: 0.9; } .gallery-item-description { - padding: 10px; - font-size: 14px; - color: #555; - background: #f9f9f9; - border-top: 1px solid #eee; + padding: 12px; + font-size: 13px; + color: var(--text-secondary); + background: var(--bg-secondary); + border-top: 1px solid var(--border-primary); + line-height: 1.5; } .gallery-item-info { display: flex; justify-content: space-between; align-items: center; - padding: 10px; - background: #fff; - border-top: 1px solid #eee; + padding: 10px 12px; + background: var(--bg-tertiary); + border-top: 1px solid var(--border-primary); } .gallery-item-date { font-size: 12px; - color: #888; + color: var(--text-secondary); } .btn-delete { - background: #dc3545; - color: white; - border: none; - padding: 5px 12px; + background: transparent; + color: var(--danger); + border: 1px solid var(--danger); + padding: 4px 10px; border-radius: 4px; font-size: 12px; cursor: pointer; - transition: background 0.2s; + transition: all 0.2s ease; + font-weight: 500; } .btn-delete:hover { - background: #c82333; + background: var(--danger); + color: var(--text-primary); } /* Upload Form Styles */ .form-group { - margin-bottom: 15px; + margin-bottom: 16px; } .form-group label { display: block; - margin-bottom: 5px; + margin-bottom: 8px; font-weight: 500; - color: #333; + color: var(--text-primary); + font-size: 14px; } .form-group input[type="file"] { width: 100%; - padding: 8px; - border: 2px dashed #ddd; - border-radius: 4px; + padding: 12px; + border: 1px dashed var(--border-primary); + border-radius: 6px; cursor: pointer; + background: var(--bg-tertiary); + color: var(--text-primary); + font-size: 14px; + transition: border-color 0.2s ease; +} + +.form-group input[type="file"]:hover { + border-color: var(--accent-primary); +} + +.form-group input[type="file"]::file-selector-button { + padding: 8px 16px; + margin-right: 12px; + background: var(--accent-primary); + color: var(--bg-primary); + border: 1px solid var(--accent-primary); + border-radius: 4px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.form-group input[type="file"]::file-selector-button:hover { + background: var(--accent-hover); + border-color: var(--accent-hover); } .form-group textarea { width: 100%; padding: 10px; - border: 1px solid #ddd; - border-radius: 4px; + border: 1px solid var(--border-primary); + border-radius: 6px; font-family: inherit; resize: vertical; + background: var(--bg-tertiary); + color: var(--text-primary); + font-size: 14px; + line-height: 1.5; + transition: border-color 0.2s ease; +} + +.form-group textarea:focus { + outline: none; + border-color: var(--accent-primary); } /* Alert Messages */ .alert { padding: 12px 16px; - border-radius: 4px; - margin-bottom: 15px; + border-radius: 6px; + margin-bottom: 16px; + font-size: 14px; } .alert-success { - background: #d4edda; - color: #155724; - border: 1px solid #c3e6cb; + background: rgba(63, 185, 80, 0.15); + color: var(--success); + border: 1px solid rgba(63, 185, 80, 0.3); } .alert-error { - background: #f8d7da; - color: #721c24; - border: 1px solid #f5c6cb; + background: rgba(248, 81, 73, 0.15); + color: var(--danger); + border: 1px solid rgba(248, 81, 73, 0.3); } /* Image Modal */ @@ -235,7 +284,8 @@ left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, 0.8); + background: rgba(0, 0, 0, 0.85); + backdrop-filter: blur(4px); } .modal-content { @@ -250,25 +300,27 @@ max-height: 90vh; display: block; border-radius: 8px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); } .modal-close { position: absolute; - top: -40px; + top: -50px; right: 0; background: transparent; border: none; - color: white; - font-size: 36px; + color: var(--text-primary); + font-size: 32px; cursor: pointer; padding: 0; width: 40px; height: 40px; - line-height: 36px; + line-height: 32px; + transition: color 0.2s ease; } .modal-close:hover { - color: #ccc; + color: var(--text-secondary); } /* Responsive Design */ diff --git a/Media.JoshHeaps.Net/wwwroot/css/site.css b/Media.JoshHeaps.Net/wwwroot/css/site.css index e69de29..e60d954 100644 --- a/Media.JoshHeaps.Net/wwwroot/css/site.css +++ b/Media.JoshHeaps.Net/wwwroot/css/site.css @@ -0,0 +1,81 @@ +/* Theme Variables */ +:root { + /* Dark Mode (Default) */ + --bg-primary: #0d1117; + --bg-secondary: #161b22; + --bg-tertiary: #1f2937; + --bg-hover: #252d3a; + --text-primary: #e6edf3; + --text-secondary: #8b949e; + --border-primary: #30363d; + --border-secondary: #21262d; + --accent-primary: #58a6ff; + --accent-hover: #4a93e3; + --danger: #f85149; + --danger-hover: #da3633; + --success: #3fb950; +} + +/* Light Mode */ +[data-theme="light"] { + --bg-primary: #ffffff; + --bg-secondary: #f6f8fa; + --bg-tertiary: #E2EFFF; + --bg-hover: #f3f4f6; + --text-primary: #24292f; + --text-secondary: #57606a; + --border-primary: #d0d7de; + --border-secondary: #e5e7eb; + --accent-primary: #0969da; + --accent-hover: #0550ae; + --danger: #cf222e; + --danger-hover: #a40e26; + --success: #1a7f37; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + background: var(--bg-primary); + color: var(--text-primary); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif; + line-height: 1.6; + min-height: 100vh; +} + +body { + margin: 0; + padding: 0; +} + +a { + color: var(--accent-primary); + text-decoration: none; + transition: color 0.2s ease; +} + +a:hover { + color: var(--accent-hover); +} + +/* Scrollbar Styling */ +::-webkit-scrollbar { + width: 12px; +} + +::-webkit-scrollbar-track { + background: var(--bg-primary); +} + +::-webkit-scrollbar-thumb { + background: var(--border-primary); + border-radius: 6px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--bg-hover); +} diff --git a/Media.JoshHeaps.Net/wwwroot/js/gallery.js b/Media.JoshHeaps.Net/wwwroot/js/gallery.js index b878842..284d215 100644 --- a/Media.JoshHeaps.Net/wwwroot/js/gallery.js +++ b/Media.JoshHeaps.Net/wwwroot/js/gallery.js @@ -7,8 +7,14 @@ document.addEventListener('DOMContentLoaded', function() { if (addFilesBtn && uploadFormCard) { addFilesBtn.addEventListener('click', () => { - uploadFormCard.style.display = 'block'; - addFilesBtn.classList.add('active'); + if (addFilesBtn.classList.contains('active')) { + uploadFormCard.style.display = 'none'; + addFilesBtn.classList.remove('active'); + } + else { + uploadFormCard.style.display = 'block'; + addFilesBtn.classList.add('active'); + } }); if (closeUploadBtn) { diff --git a/Media.JoshHeaps.Net/wwwroot/js/theme.js b/Media.JoshHeaps.Net/wwwroot/js/theme.js new file mode 100644 index 0000000..b59e698 --- /dev/null +++ b/Media.JoshHeaps.Net/wwwroot/js/theme.js @@ -0,0 +1,38 @@ +// Theme Toggle Functionality +(function() { + const THEME_KEY = 'theme-preference'; + + // Initialize theme on page load + function initTheme() { + const savedTheme = localStorage.getItem(THEME_KEY); + const theme = savedTheme || 'light'; // Default to light + + document.documentElement.setAttribute('data-theme', theme); + + // Update toggle if it exists + const toggle = document.getElementById('theme-toggle'); + if (toggle) { + toggle.checked = theme === 'dark'; + } + } + + // Toggle theme + function toggleTheme() { + const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; + const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; + + document.documentElement.setAttribute('data-theme', newTheme); + localStorage.setItem(THEME_KEY, newTheme); + } + + // Initialize immediately (before DOMContentLoaded to prevent flash) + initTheme(); + + // Set up toggle listener when DOM is ready + document.addEventListener('DOMContentLoaded', function() { + const toggle = document.getElementById('theme-toggle'); + if (toggle) { + toggle.addEventListener('change', toggleTheme); + } + }); +})();