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:
430
Views/Admin/Settings.cshtml
Executable file
430
Views/Admin/Settings.cshtml
Executable file
@@ -0,0 +1,430 @@
|
||||
<!-- Product Variant Manager Modal -->
|
||||
<div class="modal fade" id="variantManagerModal" tabindex="-1" aria-labelledby="variantManagerLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="variantManagerLabel">
|
||||
<i class="bi bi-palette"></i> Manage Color Variants
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> <strong>How it works:</strong> Create color variants and assign specific images to each color. Customers will see the assigned images when they select a color on the product page.
|
||||
</div>
|
||||
|
||||
<!-- Variant List -->
|
||||
<div id="variantList" class="mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6 class="mb-0">Color Variants (<span id="variantCount">0</span>)</h6>
|
||||
<button type="button" class="btn btn-sm btn-success" onclick="addNewVariant()">
|
||||
<i class="bi bi-plus-circle"></i> Add Color Variant
|
||||
</button>
|
||||
</div>
|
||||
<div id="variantsContainer">
|
||||
<!-- Variants will be added here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Variant Form -->
|
||||
<div id="variantForm" style="display: none;" class="border rounded p-3 bg-light">
|
||||
<h6 class="mb-3">
|
||||
<span id="formTitle">Add New Variant</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary float-end" onclick="cancelVariantForm()">
|
||||
<i class="bi bi-x"></i> Cancel
|
||||
</button>
|
||||
</h6>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Color Name *</label>
|
||||
<input type="text" id="variantColorName" class="form-control" placeholder="e.g., Ocean Blue, Cherry Red">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Color Hex Code *</label>
|
||||
<div class="input-group">
|
||||
<input type="color" id="variantColorPicker" class="form-control form-control-color" value="#3498db">
|
||||
<input type="text" id="variantColorHex" class="form-control" value="#3498db" pattern="^#[0-9A-Fa-f]{6}$">
|
||||
</div>
|
||||
<small class="text-muted">Click the color box to open the color picker</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Stock Quantity</label>
|
||||
<input type="number" id="variantStock" class="form-control" value="0" min="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Price Adjustment</label>
|
||||
<input type="number" step="0.01" id="variantPriceAdjust" class="form-control" placeholder="0.00">
|
||||
<small class="text-muted">Optional: +/- from base price</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Variant SKU</label>
|
||||
<input type="text" id="variantSKU" class="form-control" placeholder="Optional">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Assign Images to This Color *</label>
|
||||
<div class="d-flex gap-2 align-items-center mb-2">
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="selectVariantImages()">
|
||||
<i class="bi bi-images"></i> Select Images
|
||||
</button>
|
||||
<span class="text-muted" id="variantImageCount">No images selected</span>
|
||||
</div>
|
||||
<div id="variantImagePreview" class="d-flex flex-wrap gap-2">
|
||||
<!-- Selected images will appear here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="variantAvailable" checked>
|
||||
<label class="form-check-label" for="variantAvailable">
|
||||
Available for purchase
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-success" onclick="saveVariant()">
|
||||
<i class="bi bi-check-circle"></i> <span id="saveButtonText">Add Variant</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" onclick="applyVariants()">
|
||||
<i class="bi bi-save"></i> Apply Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.variant-card {
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
transition: all 0.3s;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.variant-card:hover {
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.variant-color-swatch {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #ddd;
|
||||
display: inline-block;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.variant-image-thumb {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.predefined-color-btn {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #ddd;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
display: inline-block;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.predefined-color-btn:hover {
|
||||
transform: scale(1.15);
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.predefined-color-btn.selected {
|
||||
border: 3px solid #3498db;
|
||||
box-shadow: 0 0 10px rgba(52, 152, 219, 0.5);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let productVariants = [];
|
||||
let currentEditIndex = -1;
|
||||
let currentVariantImages = [];
|
||||
let availableProductImages = [];
|
||||
|
||||
// Initialize variant manager with existing data
|
||||
function initVariantManager(existingVariants, productImages) {
|
||||
productVariants = existingVariants || [];
|
||||
availableProductImages = productImages || [];
|
||||
|
||||
// Store product images globally for image picker
|
||||
window.currentProductImages = productImages || [];
|
||||
|
||||
renderVariantList();
|
||||
}
|
||||
|
||||
// Render variant list
|
||||
function renderVariantList() {
|
||||
const container = document.getElementById('variantsContainer');
|
||||
document.getElementById('variantCount').textContent = productVariants.length;
|
||||
|
||||
if (productVariants.length === 0) {
|
||||
container.innerHTML = '<p class="text-muted text-center py-3">No color variants added yet. Click "Add Color Variant" to get started.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = productVariants.map((variant, index) => {
|
||||
const colorHex = variant.ColorHex || variant.colorHex || '#cccccc';
|
||||
const colorName = variant.ColorName || variant.colorName || 'Unknown';
|
||||
const images = variant.Images || variant.images || [];
|
||||
const stockQty = variant.StockQuantity ?? variant.stockQuantity ?? 0;
|
||||
const isAvailable = variant.IsAvailable ?? variant.isAvailable ?? true;
|
||||
|
||||
return `
|
||||
<div class="variant-card">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<div class="variant-color-swatch" style="background-color: ${colorHex};" title="${colorName}"></div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h6 class="mb-1">${colorName}</h6>
|
||||
<small class="text-muted">
|
||||
${images.length} images |
|
||||
Stock: ${stockQty} |
|
||||
${isAvailable ? '<span class="badge bg-success">Available</span>' : '<span class="badge bg-danger">Unavailable</span>'}
|
||||
</small>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
${images.slice(0, 3).map(img =>
|
||||
`<img src="${img}" class="variant-image-thumb" alt="${colorName}">`
|
||||
).join('')}
|
||||
${images.length > 3 ? `<span class="badge bg-secondary align-self-center">+${images.length - 3}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="editVariant(${index})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteVariant(${index})">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Add new variant
|
||||
function addNewVariant() {
|
||||
currentEditIndex = -1;
|
||||
currentVariantImages = [];
|
||||
document.getElementById('formTitle').textContent = 'Add New Variant';
|
||||
document.getElementById('saveButtonText').textContent = 'Add Variant';
|
||||
|
||||
// Reset form
|
||||
document.getElementById('variantColorName').value = '';
|
||||
document.getElementById('variantColorPicker').value = '#3498db';
|
||||
document.getElementById('variantColorHex').value = '#3498db';
|
||||
document.getElementById('variantStock').value = '0';
|
||||
document.getElementById('variantPriceAdjust').value = '';
|
||||
document.getElementById('variantSKU').value = '';
|
||||
document.getElementById('variantAvailable').checked = true;
|
||||
document.getElementById('variantImagePreview').innerHTML = '';
|
||||
document.getElementById('variantImageCount').textContent = 'No images selected';
|
||||
|
||||
document.getElementById('variantForm').style.display = 'block';
|
||||
}
|
||||
|
||||
// Edit variant
|
||||
function editVariant(index) {
|
||||
currentEditIndex = index;
|
||||
const variant = productVariants[index];
|
||||
|
||||
// Handle both uppercase and lowercase property names
|
||||
const colorName = variant.ColorName || variant.colorName || '';
|
||||
const colorHex = variant.ColorHex || variant.colorHex || '#3498db';
|
||||
const images = variant.Images || variant.images || [];
|
||||
const stockQty = variant.StockQuantity ?? variant.stockQuantity ?? 0;
|
||||
const priceAdj = variant.PriceAdjustment ?? variant.priceAdjustment ?? null;
|
||||
const sku = variant.SKU || variant.sku || '';
|
||||
const isAvailable = variant.IsAvailable ?? variant.isAvailable ?? true;
|
||||
|
||||
currentVariantImages = [...images];
|
||||
|
||||
document.getElementById('formTitle').textContent = 'Edit Variant';
|
||||
document.getElementById('saveButtonText').textContent = 'Update Variant';
|
||||
|
||||
document.getElementById('variantColorName').value = colorName;
|
||||
document.getElementById('variantColorPicker').value = colorHex;
|
||||
document.getElementById('variantColorHex').value = colorHex;
|
||||
document.getElementById('variantStock').value = stockQty;
|
||||
document.getElementById('variantPriceAdjust').value = priceAdj || '';
|
||||
document.getElementById('variantSKU').value = sku;
|
||||
document.getElementById('variantAvailable').checked = isAvailable;
|
||||
|
||||
updateVariantImagePreview();
|
||||
document.getElementById('variantForm').style.display = 'block';
|
||||
|
||||
console.log('[Edit Variant] Loaded variant:', { colorName, colorHex, imageCount: images.length });
|
||||
}
|
||||
|
||||
// Delete variant
|
||||
function deleteVariant(index) {
|
||||
if (confirm('Are you sure you want to delete this color variant?')) {
|
||||
productVariants.splice(index, 1);
|
||||
renderVariantList();
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel form
|
||||
function cancelVariantForm() {
|
||||
document.getElementById('variantForm').style.display = 'none';
|
||||
currentEditIndex = -1;
|
||||
currentVariantImages = [];
|
||||
}
|
||||
|
||||
// Save variant
|
||||
function saveVariant() {
|
||||
const colorName = document.getElementById('variantColorName').value.trim();
|
||||
const colorHex = document.getElementById('variantColorHex').value.trim();
|
||||
|
||||
if (!colorName || !colorHex) {
|
||||
alert('Please enter a color name and select a color.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentVariantImages.length === 0) {
|
||||
alert('Please assign at least one image to this color variant.');
|
||||
return;
|
||||
}
|
||||
|
||||
const variant = {
|
||||
ColorName: colorName,
|
||||
ColorHex: colorHex,
|
||||
Images: [...currentVariantImages],
|
||||
StockQuantity: parseInt(document.getElementById('variantStock').value) || 0,
|
||||
PriceAdjustment: parseFloat(document.getElementById('variantPriceAdjust').value) || null,
|
||||
SKU: document.getElementById('variantSKU').value.trim() || '',
|
||||
IsAvailable: document.getElementById('variantAvailable').checked
|
||||
};
|
||||
|
||||
if (currentEditIndex >= 0) {
|
||||
productVariants[currentEditIndex] = variant;
|
||||
} else {
|
||||
productVariants.push(variant);
|
||||
}
|
||||
|
||||
renderVariantList();
|
||||
cancelVariantForm();
|
||||
}
|
||||
|
||||
// Select images for variant
|
||||
function selectVariantImages() {
|
||||
console.log('selectVariantImages called');
|
||||
|
||||
// Check if openImagePicker function exists
|
||||
if (typeof openImagePicker !== 'function') {
|
||||
console.error('openImagePicker function not found!');
|
||||
alert('Error: Image picker function not available. Please ensure the page has fully loaded.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Calling openImagePicker...');
|
||||
openImagePicker(function(selectedUrls) {
|
||||
console.log('Image picker callback received:', selectedUrls);
|
||||
currentVariantImages = selectedUrls;
|
||||
updateVariantImagePreview();
|
||||
}, 'multiple');
|
||||
}
|
||||
|
||||
// Update variant image preview
|
||||
function updateVariantImagePreview() {
|
||||
const preview = document.getElementById('variantImagePreview');
|
||||
const count = document.getElementById('variantImageCount');
|
||||
|
||||
if (currentVariantImages.length === 0) {
|
||||
preview.innerHTML = '';
|
||||
count.textContent = 'No images selected';
|
||||
return;
|
||||
}
|
||||
|
||||
count.textContent = `${currentVariantImages.length} image(s) selected`;
|
||||
preview.innerHTML = currentVariantImages.map((img, idx) => `
|
||||
<div class="position-relative">
|
||||
<img src="${img}" class="variant-image-thumb" alt="Variant image">
|
||||
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0"
|
||||
style="padding: 2px 6px; font-size: 0.7rem;"
|
||||
onclick="removeVariantImage(${idx})">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Remove variant image
|
||||
function removeVariantImage(index) {
|
||||
currentVariantImages.splice(index, 1);
|
||||
updateVariantImagePreview();
|
||||
}
|
||||
|
||||
// Sync color picker and hex input
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const colorPicker = document.getElementById('variantColorPicker');
|
||||
const colorHex = document.getElementById('variantColorHex');
|
||||
|
||||
if (colorPicker && colorHex) {
|
||||
colorPicker.addEventListener('input', function() {
|
||||
colorHex.value = this.value;
|
||||
});
|
||||
|
||||
colorHex.addEventListener('input', function() {
|
||||
if (/^#[0-9A-Fa-f]{6}$/.test(this.value)) {
|
||||
colorPicker.value = this.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Apply variants (to be called when modal is closed)
|
||||
function applyVariants() {
|
||||
// Store variants in hidden field
|
||||
document.getElementById('productVariantsData').value = JSON.stringify(productVariants);
|
||||
bootstrap.Modal.getInstance(document.getElementById('variantManagerModal')).hide();
|
||||
|
||||
// Update UI to show variant count
|
||||
updateVariantSummary();
|
||||
}
|
||||
|
||||
// Update variant summary on main form
|
||||
function updateVariantSummary() {
|
||||
const summary = document.getElementById('variantSummary');
|
||||
if (summary) {
|
||||
summary.innerHTML = productVariants.length > 0
|
||||
? `<span class="badge bg-success">${productVariants.length} color variants configured</span>`
|
||||
: '<span class="badge bg-secondary">No variants</span>';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user