Files
SkyArtShop/Views/Admin/Settings.cshtml
Local Server 703ab57984 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
2025-12-13 22:34:11 -06:00

431 lines
18 KiB
Plaintext
Executable File

<!-- 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>