Fix admin route access and backend configuration
- Added /admin redirect to login page in nginx config - Fixed backend server.js route ordering for proper admin handling - Updated authentication middleware and routes - Added user management routes - Configured PostgreSQL integration - Updated environment configuration
This commit is contained in:
446
Views/Home/Index.cshtml
Executable file
446
Views/Home/Index.cshtml
Executable file
@@ -0,0 +1,446 @@
|
||||
<!-- Image Picker Modal -->
|
||||
<div class="modal fade" id="imagePickerModal" tabindex="-1" aria-labelledby="imagePickerModalLabel" aria-hidden="true" data-bs-backdrop="true" style="z-index: 1060;">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="imagePickerModalLabel">
|
||||
<i class="bi bi-images"></i> Select or Upload Images
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Tab Navigation -->
|
||||
<ul class="nav nav-tabs mb-3" id="imagePickerTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="library-tab" data-bs-toggle="tab" data-bs-target="#library" type="button" role="tab">
|
||||
<i class="bi bi-folder2-open"></i> Image Library
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="upload-tab" data-bs-toggle="tab" data-bs-target="#upload" type="button" role="tab">
|
||||
<i class="bi bi-cloud-upload"></i> Upload New
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="tab-content" id="imagePickerTabContent">
|
||||
<!-- Image Library Tab -->
|
||||
<div class="tab-pane fade show active" id="library" role="tabpanel">
|
||||
<div class="mb-3">
|
||||
<div class="btn-group w-100 mb-2" role="group">
|
||||
<button type="button" class="btn btn-outline-primary" id="showProductImagesBtn" onclick="showProductImages()">Product Images (<span id="productImageCount">0</span>)</button>
|
||||
<button type="button" class="btn btn-outline-secondary active" id="showAllImagesBtn" onclick="showAllImages()">All Images</button>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="imageSearchInput" placeholder="Search images..." onkeyup="filterImages()">
|
||||
</div>
|
||||
<div id="imageLibraryLoading" class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2 text-muted">Loading images...</p>
|
||||
</div>
|
||||
<div id="imageLibraryContent" style="display: none;">
|
||||
<div class="row g-2" id="imageLibraryGrid" style="max-height: 500px; overflow-y: auto;">
|
||||
<!-- Images will be loaded here dynamically -->
|
||||
</div>
|
||||
<div id="noImagesMessage" class="text-center py-5" style="display: none;">
|
||||
<i class="bi bi-images" style="font-size: 48px; color: #ccc;"></i>
|
||||
<p class="text-muted mt-2">No images found in library</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Tab -->
|
||||
<div class="tab-pane fade" id="upload" role="tabpanel">
|
||||
<div class="border rounded p-4 text-center" style="border-style: dashed !important;">
|
||||
<i class="bi bi-cloud-arrow-up" style="font-size: 48px; color: #0d6efd;"></i>
|
||||
<h5 class="mt-3">Upload Images</h5>
|
||||
<p class="text-muted">Drag and drop files here or click to browse</p>
|
||||
<input type="file" class="form-control" id="newImageUpload" accept="image/*" multiple style="display: none;">
|
||||
<button type="button" class="btn btn-primary" onclick="document.getElementById('newImageUpload').click()">
|
||||
<i class="bi bi-folder2-open"></i> Choose Files
|
||||
</button>
|
||||
<small class="d-block mt-2 text-muted">Supported formats: JPG, PNG, GIF, WEBP</small>
|
||||
</div>
|
||||
<div id="uploadProgress" class="mt-3" style="display: none;">
|
||||
<div class="progress">
|
||||
<div id="uploadProgressBar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
<small class="text-muted" id="uploadProgressText">Uploading...</small>
|
||||
</div>
|
||||
<div id="uploadedImagesPreview" class="row g-2 mt-3">
|
||||
<!-- Uploaded images preview -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="selectImagesBtn" onclick="confirmImageSelection()">
|
||||
<i class="bi bi-check-lg"></i> Select Images (<span id="selectedCount">0</span>)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Ensure nested modals work correctly */
|
||||
.modal-backdrop.show:nth-of-type(2) {
|
||||
z-index: 1055;
|
||||
}
|
||||
|
||||
#imagePickerModal.show {
|
||||
z-index: 1060 !important;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
z-index: 1050;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let selectedImages = [];
|
||||
let imagePickerCallback = null;
|
||||
let imagePickerMode = 'multiple'; // 'single' or 'multiple'
|
||||
let allLibraryImages = [];
|
||||
let currentViewMode = 'all'; // 'product' or 'all'
|
||||
|
||||
// Open Image Picker Modal
|
||||
function openImagePicker(callback, mode = 'multiple') {
|
||||
imagePickerCallback = callback;
|
||||
imagePickerMode = mode;
|
||||
selectedImages = [];
|
||||
updateSelectedCount();
|
||||
|
||||
// Update product image count
|
||||
const productImages = window.currentProductImages || [];
|
||||
document.getElementById('productImageCount').textContent = productImages.length;
|
||||
|
||||
// Show modal - use getOrCreateInstance to handle existing instances
|
||||
const modalElement = document.getElementById('imagePickerModal');
|
||||
if (!modalElement) {
|
||||
console.error('Image Picker Modal element not found!');
|
||||
alert('Error: Image picker not available. Please refresh the page.');
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalElement);
|
||||
modal.show();
|
||||
|
||||
// Start with product images if available, otherwise all images
|
||||
if (productImages.length > 0) {
|
||||
currentViewMode = 'product';
|
||||
showProductImages();
|
||||
} else {
|
||||
currentViewMode = 'all';
|
||||
loadImageLibrary();
|
||||
}
|
||||
}
|
||||
|
||||
// Load Image Library
|
||||
async function loadImageLibrary() {
|
||||
const loading = document.getElementById('imageLibraryLoading');
|
||||
const content = document.getElementById('imageLibraryContent');
|
||||
const grid = document.getElementById('imageLibraryGrid');
|
||||
const noImagesMsg = document.getElementById('noImagesMessage');
|
||||
|
||||
loading.style.display = 'block';
|
||||
content.style.display = 'none';
|
||||
|
||||
try {
|
||||
const response = await fetch('/admin/upload/list');
|
||||
const images = await response.json();
|
||||
allLibraryImages = images;
|
||||
|
||||
loading.style.display = 'none';
|
||||
content.style.display = 'block';
|
||||
|
||||
if (images.length === 0) {
|
||||
grid.style.display = 'none';
|
||||
noImagesMsg.style.display = 'block';
|
||||
} else {
|
||||
grid.style.display = 'flex';
|
||||
noImagesMsg.style.display = 'none';
|
||||
renderImageLibrary(images);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading images:', error);
|
||||
loading.innerHTML = '<div class="alert alert-danger">Error loading images</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Render Image Library
|
||||
function renderImageLibrary(images) {
|
||||
const grid = document.getElementById('imageLibraryGrid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
images.forEach(imageUrl => {
|
||||
const col = document.createElement('div');
|
||||
col.className = 'col-6 col-md-3 col-lg-2';
|
||||
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.className = 'image-library-item position-relative';
|
||||
itemDiv.setAttribute('data-image-url', imageUrl);
|
||||
itemDiv.style.cssText = 'cursor: pointer; border: 3px solid transparent; border-radius: 8px; overflow: hidden; transition: all 0.2s;';
|
||||
|
||||
itemDiv.innerHTML = `
|
||||
<img src="${imageUrl}" class="img-fluid" style="width: 100%; height: 120px; object-fit: cover; display: block;">
|
||||
<div class="image-overlay position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center"
|
||||
style="background: rgba(0,0,0,0.5); opacity: 0; transition: opacity 0.2s;">
|
||||
<i class="bi bi-check-circle-fill text-white" style="font-size: 2rem;"></i>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Use event listener instead of inline onclick to avoid URL escaping issues
|
||||
itemDiv.addEventListener('click', function() {
|
||||
toggleImageSelection(imageUrl, this);
|
||||
});
|
||||
|
||||
col.appendChild(itemDiv);
|
||||
grid.appendChild(col);
|
||||
});
|
||||
}
|
||||
|
||||
// Show Product Images
|
||||
function showProductImages() {
|
||||
currentViewMode = 'product';
|
||||
const productImages = window.currentProductImages || [];
|
||||
|
||||
// Update button states
|
||||
document.getElementById('showProductImagesBtn').classList.add('active');
|
||||
document.getElementById('showProductImagesBtn').classList.remove('btn-outline-primary');
|
||||
document.getElementById('showProductImagesBtn').classList.add('btn-primary');
|
||||
document.getElementById('showAllImagesBtn').classList.remove('active', 'btn-primary');
|
||||
document.getElementById('showAllImagesBtn').classList.add('btn-outline-secondary');
|
||||
|
||||
const loading = document.getElementById('imageLibraryLoading');
|
||||
const content = document.getElementById('imageLibraryContent');
|
||||
const grid = document.getElementById('imageLibraryGrid');
|
||||
const noImagesMsg = document.getElementById('noImagesMessage');
|
||||
|
||||
loading.style.display = 'none';
|
||||
content.style.display = 'block';
|
||||
|
||||
if (productImages.length === 0) {
|
||||
grid.style.display = 'none';
|
||||
noImagesMsg.style.display = 'block';
|
||||
noImagesMsg.innerHTML = '<i class=\"bi bi-images\" style=\"font-size: 48px; color: #ccc;\"></i><p class=\"text-muted mt-2\">No images added to this product yet. Switch to \"All Images\" to browse the full library.</p>';
|
||||
} else {
|
||||
grid.style.display = 'flex';
|
||||
noImagesMsg.style.display = 'none';
|
||||
renderImageLibrary(productImages);
|
||||
}
|
||||
}
|
||||
|
||||
// Show All Images
|
||||
function showAllImages() {
|
||||
currentViewMode = 'all';
|
||||
|
||||
// Update button states
|
||||
document.getElementById('showAllImagesBtn').classList.add('active');
|
||||
document.getElementById('showAllImagesBtn').classList.remove('btn-outline-secondary');
|
||||
document.getElementById('showAllImagesBtn').classList.add('btn-primary');
|
||||
document.getElementById('showProductImagesBtn').classList.remove('active', 'btn-primary');
|
||||
document.getElementById('showProductImagesBtn').classList.add('btn-outline-primary');
|
||||
|
||||
loadImageLibrary();
|
||||
}
|
||||
|
||||
// Filter Images
|
||||
function filterImages() {
|
||||
const searchTerm = document.getElementById('imageSearchInput').value.toLowerCase();
|
||||
|
||||
if (currentViewMode === 'product') {
|
||||
const productImages = window.currentProductImages || [];
|
||||
const filteredImages = productImages.filter(url => url.toLowerCase().includes(searchTerm));
|
||||
renderImageLibrary(filteredImages);
|
||||
} else {
|
||||
const filteredImages = allLibraryImages.filter(url => url.toLowerCase().includes(searchTerm));
|
||||
// Toggle Image Selection
|
||||
function toggleImageSelection(imageUrl, element) {
|
||||
if (imagePickerMode === 'single') {
|
||||
// Single selection mode
|
||||
selectedImages = [imageUrl];
|
||||
// Remove selection from all
|
||||
document.querySelectorAll('.image-library-item').forEach(item => {
|
||||
item.style.borderColor = 'transparent';
|
||||
item.querySelector('.image-overlay').style.opacity = '0';
|
||||
});
|
||||
// Add selection to clicked
|
||||
element.style.borderColor = '#0d6efd';
|
||||
element.querySelector('.image-overlay').style.opacity = '1';
|
||||
} else {
|
||||
// Multiple selection mode
|
||||
const index = selectedImages.indexOf(imageUrl);
|
||||
if (index > -1) {
|
||||
selectedImages.splice(index, 1);
|
||||
element.style.borderColor = 'transparent';
|
||||
element.querySelector('.image-overlay').style.opacity = '0';
|
||||
} else {
|
||||
selectedImages.push(imageUrl);
|
||||
element.style.borderColor = '#0d6efd';
|
||||
element.querySelector('.image-overlay').style.opacity = '1';
|
||||
}
|
||||
}
|
||||
updateSelectedCount();
|
||||
}
|
||||
|
||||
// Update Selected Count
|
||||
function updateSelectedCount() {
|
||||
document.getElementById('selectedCount').textContent = selectedImages.length;
|
||||
document.getElementById('selectImagesBtn').disabled = selectedImages.length === 0;
|
||||
}
|
||||
|
||||
// Confirm Image Selection
|
||||
function confirmImageSelection() {
|
||||
if (selectedImages.length === 0) {
|
||||
alert('Please select at least one image.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!imagePickerCallback) {
|
||||
console.error('No callback function defined!');
|
||||
alert('Error: Callback function not found. Please close and try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
imagePickerCallback(selectedImages);
|
||||
const modalElement = document.getElementById('imagePickerModal');
|
||||
const modalInstance = bootstrap.Modal.getInstance(modalElement);
|
||||
if (modalInstance) {
|
||||
modalInstance.hide();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in callback:', error);
|
||||
alert('Error processing selection: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle New Image Upload
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const uploadInput = document.getElementById('newImageUpload');
|
||||
if (uploadInput) {
|
||||
uploadInput.addEventListener('change', handleNewImageUpload);
|
||||
}
|
||||
|
||||
// Drag and drop support
|
||||
const uploadArea = document.querySelector('#upload .border');
|
||||
if (uploadArea) {
|
||||
uploadArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.style.backgroundColor = '#e7f3ff';
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('dragleave', () => {
|
||||
uploadArea.style.backgroundColor = '';
|
||||
});
|
||||
|
||||
uploadArea.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.style.backgroundColor = '';
|
||||
const files = e.dataTransfer.files;
|
||||
handleNewImageUpload({ target: { files } });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle New Image Upload
|
||||
async function handleNewImageUpload(event) {
|
||||
const files = event.target.files;
|
||||
if (files.length === 0) return;
|
||||
|
||||
const progressDiv = document.getElementById('uploadProgress');
|
||||
const progressBar = document.getElementById('uploadProgressBar');
|
||||
const progressText = document.getElementById('uploadProgressText');
|
||||
const previewDiv = document.getElementById('uploadedImagesPreview');
|
||||
|
||||
progressDiv.style.display = 'block';
|
||||
previewDiv.innerHTML = '';
|
||||
|
||||
const uploadedUrls = [];
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
progressText.textContent = `Uploading ${i + 1} of ${files.length}...`;
|
||||
progressBar.style.width = ((i / files.length) * 100) + '%';
|
||||
|
||||
try {
|
||||
const response = await fetch('/admin/upload/image', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
uploadedUrls.push(result.url);
|
||||
|
||||
// Add to preview
|
||||
const col = document.createElement('div');
|
||||
col.className = 'col-4';
|
||||
col.innerHTML = `
|
||||
<div class="position-relative">
|
||||
<img src="${result.url}" class="img-fluid rounded" style="width: 100%; height: 100px; object-fit: cover;">
|
||||
<span class="badge bg-success position-absolute top-0 end-0 m-1">
|
||||
<i class="bi bi-check-lg"></i>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
previewDiv.appendChild(col);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
progressBar.style.width = '100%';
|
||||
progressText.textContent = `Successfully uploaded ${uploadedUrls.length} images`;
|
||||
|
||||
// Auto-select uploaded images
|
||||
selectedImages = uploadedUrls;
|
||||
updateSelectedCount();
|
||||
|
||||
// Reload library
|
||||
setTimeout(() => {
|
||||
loadImageLibrary();
|
||||
// Switch to library tab
|
||||
document.getElementById('library-tab').click();
|
||||
}, 1000);
|
||||
|
||||
// Reset input
|
||||
event.target.value = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.image-library-item:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.image-library-item:hover .image-overlay {
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
#imageLibraryGrid {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #0d6efd #f8f9fa;
|
||||
}
|
||||
|
||||
#imageLibraryGrid::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#imageLibraryGrid::-webkit-scrollbar-track {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
#imageLibraryGrid::-webkit-scrollbar-thumb {
|
||||
background: #0d6efd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user