Add production deployment infrastructure
- Created comprehensive deployment package with production builds - Added deployment scripts for Linux and Docker environments - Generated Dockerfiles for containerized deployment - Included nginx reverse proxy configuration - Added systemd service definitions for native Linux deployment - Created docker-compose.production.yml for orchestration - Comprehensive deployment documentation in README.md - Both LittleShop and TeleBot production builds included - Ready for deployment to Hostinger VPS server 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
221
Deploy/LittleShop/wwwroot/js/modern-mobile.js
Normal file
221
Deploy/LittleShop/wwwroot/js/modern-mobile.js
Normal file
@@ -0,0 +1,221 @@
|
||||
// Modern Mobile Enhancements
|
||||
// Clean, simple mobile-friendly functionality
|
||||
|
||||
class ModernMobile {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupMobileTableLabels();
|
||||
this.setupResponsiveNavigation();
|
||||
this.setupFormEnhancements();
|
||||
this.setupSmoothInteractions();
|
||||
}
|
||||
|
||||
// Add data labels for mobile table stacking
|
||||
setupMobileTableLabels() {
|
||||
const tables = document.querySelectorAll('.table');
|
||||
|
||||
tables.forEach(table => {
|
||||
const headers = Array.from(table.querySelectorAll('thead th')).map(th => th.textContent.trim());
|
||||
const rows = table.querySelectorAll('tbody tr');
|
||||
|
||||
rows.forEach(row => {
|
||||
const cells = row.querySelectorAll('td');
|
||||
cells.forEach((cell, index) => {
|
||||
if (headers[index]) {
|
||||
cell.setAttribute('data-label', headers[index]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Enhanced mobile navigation
|
||||
setupResponsiveNavigation() {
|
||||
const navbar = document.querySelector('.navbar');
|
||||
const toggler = document.querySelector('.navbar-toggler');
|
||||
const collapse = document.querySelector('.navbar-collapse');
|
||||
|
||||
if (toggler && collapse) {
|
||||
// Smooth collapse animation
|
||||
toggler.addEventListener('click', () => {
|
||||
collapse.classList.toggle('show');
|
||||
});
|
||||
|
||||
// Close menu when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!navbar.contains(e.target) && collapse.classList.contains('show')) {
|
||||
collapse.classList.remove('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Form enhancements for better mobile UX
|
||||
setupFormEnhancements() {
|
||||
// Auto-focus first input on desktop
|
||||
if (window.innerWidth > 768) {
|
||||
const firstInput = document.querySelector('.form-control:not([readonly]):not([disabled])');
|
||||
if (firstInput) {
|
||||
firstInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced form validation feedback
|
||||
const forms = document.querySelectorAll('form');
|
||||
forms.forEach(form => {
|
||||
form.addEventListener('submit', (e) => {
|
||||
// Skip validation for file upload forms
|
||||
if (form.enctype === 'multipart/form-data') {
|
||||
console.log('Mobile: Skipping validation for file upload form');
|
||||
return;
|
||||
}
|
||||
|
||||
const invalidInputs = form.querySelectorAll(':invalid');
|
||||
if (invalidInputs.length > 0) {
|
||||
e.preventDefault();
|
||||
console.log('Mobile: Form validation failed, focusing on first invalid input');
|
||||
invalidInputs[0].focus();
|
||||
invalidInputs[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Floating labels effect
|
||||
const inputs = document.querySelectorAll('.form-control, .form-select');
|
||||
inputs.forEach(input => {
|
||||
if (input.value) {
|
||||
input.parentElement.classList.add('has-value');
|
||||
}
|
||||
|
||||
input.addEventListener('blur', () => {
|
||||
if (input.value) {
|
||||
input.parentElement.classList.add('has-value');
|
||||
} else {
|
||||
input.parentElement.classList.remove('has-value');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Smooth interactions and feedback
|
||||
setupSmoothInteractions() {
|
||||
// Button click feedback
|
||||
const buttons = document.querySelectorAll('.btn');
|
||||
buttons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
if (!button.disabled) {
|
||||
button.style.transform = 'scale(0.95)';
|
||||
setTimeout(() => {
|
||||
button.style.transform = '';
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Card hover enhancement
|
||||
const cards = document.querySelectorAll('.card');
|
||||
cards.forEach(card => {
|
||||
card.addEventListener('mouseenter', () => {
|
||||
card.style.transform = 'translateY(-2px)';
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', () => {
|
||||
card.style.transform = '';
|
||||
});
|
||||
});
|
||||
|
||||
// Smooth scroll for anchor links
|
||||
const anchorLinks = document.querySelectorAll('a[href^="#"]');
|
||||
anchorLinks.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(link.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Show toast notification
|
||||
showToast(message, type = 'info') {
|
||||
const toastContainer = document.getElementById('toast-container') || this.createToastContainer();
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `alert alert-${type} alert-dismissible fade show`;
|
||||
toast.style.cssText = `
|
||||
margin-bottom: 0.5rem;
|
||||
animation: slideInRight 0.3s ease;
|
||||
`;
|
||||
|
||||
toast.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" aria-label="Close"></button>
|
||||
`;
|
||||
|
||||
toastContainer.appendChild(toast);
|
||||
|
||||
// Auto-remove after 4 seconds
|
||||
setTimeout(() => {
|
||||
toast.classList.add('fade');
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.parentNode.removeChild(toast);
|
||||
}
|
||||
}, 150);
|
||||
}, 4000);
|
||||
|
||||
// Manual close
|
||||
const closeBtn = toast.querySelector('.btn-close');
|
||||
closeBtn.addEventListener('click', () => {
|
||||
toast.classList.add('fade');
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.parentNode.removeChild(toast);
|
||||
}
|
||||
}, 150);
|
||||
});
|
||||
}
|
||||
|
||||
createToastContainer() {
|
||||
const container = document.createElement('div');
|
||||
container.id = 'toast-container';
|
||||
container.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 1050;
|
||||
max-width: 300px;
|
||||
`;
|
||||
document.body.appendChild(container);
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
// CSS for toast animations
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.fade {
|
||||
opacity: 0 !important;
|
||||
transition: opacity 0.15s linear !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Initialize
|
||||
const modernMobile = new ModernMobile();
|
||||
window.modernMobile = modernMobile;
|
||||
Reference in New Issue
Block a user