admin and vendor backends features
This commit is contained in:
581
static/js/shared/alpine-components.js
Normal file
581
static/js/shared/alpine-components.js
Normal file
@@ -0,0 +1,581 @@
|
||||
/**
|
||||
* Alpine.js Components for Multi-Tenant E-commerce Platform
|
||||
* Universal component system for Admin, Vendor, and Shop sections
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// BASE MODAL SYSTEM
|
||||
// Universal modal functions used by all sections
|
||||
// =============================================================================
|
||||
|
||||
window.baseModalSystem = function() {
|
||||
return {
|
||||
// Confirmation Modal State
|
||||
confirmModal: {
|
||||
show: false,
|
||||
title: '',
|
||||
message: '',
|
||||
warning: '',
|
||||
buttonText: 'Confirm',
|
||||
buttonClass: 'btn-danger',
|
||||
onConfirm: null,
|
||||
onCancel: null
|
||||
},
|
||||
|
||||
// Success Modal State
|
||||
successModal: {
|
||||
show: false,
|
||||
title: 'Success',
|
||||
message: '',
|
||||
redirectUrl: null,
|
||||
redirectDelay: 2000
|
||||
},
|
||||
|
||||
// Error Modal State
|
||||
errorModal: {
|
||||
show: false,
|
||||
title: 'Error',
|
||||
message: '',
|
||||
details: ''
|
||||
},
|
||||
|
||||
// Loading State
|
||||
loading: false,
|
||||
|
||||
/**
|
||||
* Show confirmation modal
|
||||
* @param {Object} options - Modal configuration
|
||||
*/
|
||||
showConfirmModal(options) {
|
||||
this.confirmModal = {
|
||||
show: true,
|
||||
title: options.title || 'Confirm Action',
|
||||
message: options.message || 'Are you sure?',
|
||||
warning: options.warning || '',
|
||||
buttonText: options.buttonText || 'Confirm',
|
||||
buttonClass: options.buttonClass || 'btn-danger',
|
||||
onConfirm: options.onConfirm || null,
|
||||
onCancel: options.onCancel || null
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Close confirmation modal
|
||||
*/
|
||||
closeConfirmModal() {
|
||||
if (this.confirmModal.onCancel) {
|
||||
this.confirmModal.onCancel();
|
||||
}
|
||||
this.confirmModal.show = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle confirmation action
|
||||
*/
|
||||
async handleConfirm() {
|
||||
if (this.confirmModal.onConfirm) {
|
||||
this.closeConfirmModal();
|
||||
await this.confirmModal.onConfirm();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show success modal
|
||||
* @param {Object} options - Modal configuration
|
||||
*/
|
||||
showSuccessModal(options) {
|
||||
this.successModal = {
|
||||
show: true,
|
||||
title: options.title || 'Success',
|
||||
message: options.message || 'Operation completed successfully',
|
||||
redirectUrl: options.redirectUrl || null,
|
||||
redirectDelay: options.redirectDelay || 2000
|
||||
};
|
||||
|
||||
// Auto-redirect if URL provided
|
||||
if (this.successModal.redirectUrl) {
|
||||
setTimeout(() => {
|
||||
window.location.href = this.successModal.redirectUrl;
|
||||
}, this.successModal.redirectDelay);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Close success modal
|
||||
*/
|
||||
closeSuccessModal() {
|
||||
this.successModal.show = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show error modal
|
||||
* @param {Object} options - Modal configuration
|
||||
*/
|
||||
showErrorModal(options) {
|
||||
this.errorModal = {
|
||||
show: true,
|
||||
title: options.title || 'Error',
|
||||
message: options.message || 'An error occurred',
|
||||
details: options.details || ''
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Close error modal
|
||||
*/
|
||||
closeErrorModal() {
|
||||
this.errorModal.show = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show loading overlay
|
||||
*/
|
||||
showLoading() {
|
||||
this.loading = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide loading overlay
|
||||
*/
|
||||
hideLoading() {
|
||||
this.loading = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// ADMIN LAYOUT COMPONENT
|
||||
// Header, Sidebar, Navigation, Modals for Admin Section
|
||||
// =============================================================================
|
||||
|
||||
window.adminLayout = function() {
|
||||
return {
|
||||
...window.baseModalSystem(),
|
||||
|
||||
// Admin-specific state
|
||||
user: null,
|
||||
menuOpen: false,
|
||||
currentPage: '',
|
||||
|
||||
/**
|
||||
* Initialize admin layout
|
||||
*/
|
||||
async init() {
|
||||
this.currentPage = this.getCurrentPage();
|
||||
await this.loadUserData();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load current admin user data
|
||||
*/
|
||||
async loadUserData() {
|
||||
try {
|
||||
const response = await apiClient.get('/admin/auth/me');
|
||||
this.user = response;
|
||||
} catch (error) {
|
||||
console.error('Failed to load user data:', error);
|
||||
// Redirect to login if not authenticated
|
||||
if (error.status === 401) {
|
||||
window.location.href = '/admin/login.html';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current page name from URL
|
||||
*/
|
||||
getCurrentPage() {
|
||||
const path = window.location.pathname;
|
||||
const page = path.split('/').pop().replace('.html', '');
|
||||
return page || 'dashboard';
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if menu item is active
|
||||
*/
|
||||
isActive(page) {
|
||||
return this.currentPage === page;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle mobile menu
|
||||
*/
|
||||
toggleMenu() {
|
||||
this.menuOpen = !this.menuOpen;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show logout confirmation
|
||||
*/
|
||||
confirmLogout() {
|
||||
this.showConfirmModal({
|
||||
title: 'Confirm Logout',
|
||||
message: 'Are you sure you want to logout?',
|
||||
buttonText: 'Logout',
|
||||
buttonClass: 'btn-primary',
|
||||
onConfirm: () => this.logout()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform logout
|
||||
*/
|
||||
async logout() {
|
||||
try {
|
||||
this.showLoading();
|
||||
await apiClient.post('/admin/auth/logout');
|
||||
window.location.href = '/admin/login.html';
|
||||
} catch (error) {
|
||||
this.hideLoading();
|
||||
this.showErrorModal({
|
||||
message: 'Logout failed',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// VENDOR LAYOUT COMPONENT
|
||||
// Header, Sidebar, Navigation, Modals for Vendor Dashboard
|
||||
// =============================================================================
|
||||
|
||||
window.vendorLayout = function() {
|
||||
return {
|
||||
...window.baseModalSystem(),
|
||||
|
||||
// Vendor-specific state
|
||||
user: null,
|
||||
vendor: null,
|
||||
menuOpen: false,
|
||||
currentPage: '',
|
||||
|
||||
/**
|
||||
* Initialize vendor layout
|
||||
*/
|
||||
async init() {
|
||||
this.currentPage = this.getCurrentPage();
|
||||
await this.loadUserData();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load current vendor user data
|
||||
*/
|
||||
async loadUserData() {
|
||||
try {
|
||||
const response = await apiClient.get('/vendor/auth/me');
|
||||
this.user = response.user;
|
||||
this.vendor = response.vendor;
|
||||
} catch (error) {
|
||||
console.error('Failed to load user data:', error);
|
||||
if (error.status === 401) {
|
||||
window.location.href = '/vendor/login.html';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current page name from URL
|
||||
*/
|
||||
getCurrentPage() {
|
||||
const path = window.location.pathname;
|
||||
const page = path.split('/').pop().replace('.html', '');
|
||||
return page || 'dashboard';
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if menu item is active
|
||||
*/
|
||||
isActive(page) {
|
||||
return this.currentPage === page;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle mobile menu
|
||||
*/
|
||||
toggleMenu() {
|
||||
this.menuOpen = !this.menuOpen;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show logout confirmation
|
||||
*/
|
||||
confirmLogout() {
|
||||
this.showConfirmModal({
|
||||
title: 'Confirm Logout',
|
||||
message: 'Are you sure you want to logout?',
|
||||
buttonText: 'Logout',
|
||||
buttonClass: 'btn-primary',
|
||||
onConfirm: () => this.logout()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform logout
|
||||
*/
|
||||
async logout() {
|
||||
try {
|
||||
this.showLoading();
|
||||
await apiClient.post('/vendor/auth/logout');
|
||||
window.location.href = '/vendor/login.html';
|
||||
} catch (error) {
|
||||
this.hideLoading();
|
||||
this.showErrorModal({
|
||||
message: 'Logout failed',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// SHOP LAYOUT COMPONENT
|
||||
// Header, Cart, Search, Navigation for Customer-Facing Shop
|
||||
// =============================================================================
|
||||
|
||||
window.shopLayout = function() {
|
||||
return {
|
||||
...window.baseModalSystem(),
|
||||
|
||||
// Shop-specific state
|
||||
vendor: null,
|
||||
cart: null,
|
||||
cartCount: 0,
|
||||
sessionId: null,
|
||||
searchQuery: '',
|
||||
mobileMenuOpen: false,
|
||||
|
||||
/**
|
||||
* Initialize shop layout
|
||||
*/
|
||||
async init() {
|
||||
this.sessionId = this.getOrCreateSessionId();
|
||||
await this.detectVendor();
|
||||
if (this.vendor) {
|
||||
await this.loadCart();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Detect vendor from subdomain or vendor code
|
||||
*/
|
||||
async detectVendor() {
|
||||
try {
|
||||
const hostname = window.location.hostname;
|
||||
const subdomain = hostname.split('.')[0];
|
||||
|
||||
// Try to get vendor by subdomain first
|
||||
if (subdomain && subdomain !== 'localhost' && subdomain !== 'www') {
|
||||
this.vendor = await apiClient.get(`/public/vendors/by-subdomain/${subdomain}`);
|
||||
} else {
|
||||
// Fallback: Try to get vendor code from URL or localStorage
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const vendorCode = urlParams.get('vendor') || localStorage.getItem('vendorCode');
|
||||
|
||||
if (vendorCode) {
|
||||
this.vendor = await apiClient.get(`/public/vendors/by-code/${vendorCode}`);
|
||||
localStorage.setItem('vendorCode', vendorCode);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to detect vendor:', error);
|
||||
this.showErrorModal({
|
||||
message: 'Vendor not found',
|
||||
details: 'Unable to identify the store. Please check the URL.'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get or create session ID for cart
|
||||
*/
|
||||
getOrCreateSessionId() {
|
||||
let sessionId = localStorage.getItem('cartSessionId');
|
||||
if (!sessionId) {
|
||||
sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
localStorage.setItem('cartSessionId', sessionId);
|
||||
}
|
||||
return sessionId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Load cart from API
|
||||
*/
|
||||
async loadCart() {
|
||||
if (!this.vendor) return;
|
||||
|
||||
try {
|
||||
this.cart = await apiClient.get(
|
||||
`/public/vendors/${this.vendor.id}/cart/${this.sessionId}`
|
||||
);
|
||||
this.updateCartCount();
|
||||
} catch (error) {
|
||||
console.error('Failed to load cart:', error);
|
||||
this.cart = { items: [] };
|
||||
this.cartCount = 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update cart item count
|
||||
*/
|
||||
updateCartCount() {
|
||||
if (this.cart && this.cart.items) {
|
||||
this.cartCount = this.cart.items.reduce((sum, item) => sum + item.quantity, 0);
|
||||
} else {
|
||||
this.cartCount = 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add item to cart
|
||||
*/
|
||||
async addToCart(productId, quantity = 1) {
|
||||
if (!this.vendor) {
|
||||
this.showErrorModal({ message: 'Vendor not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.showLoading();
|
||||
await apiClient.post(
|
||||
`/public/vendors/${this.vendor.id}/cart/${this.sessionId}/items`,
|
||||
{ product_id: productId, quantity }
|
||||
);
|
||||
await this.loadCart();
|
||||
this.hideLoading();
|
||||
this.showSuccessModal({
|
||||
title: 'Added to Cart',
|
||||
message: 'Product added successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
this.hideLoading();
|
||||
this.showErrorModal({
|
||||
message: 'Failed to add to cart',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle mobile menu
|
||||
*/
|
||||
toggleMobileMenu() {
|
||||
this.mobileMenuOpen = !this.mobileMenuOpen;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle search
|
||||
*/
|
||||
handleSearch() {
|
||||
if (this.searchQuery.trim()) {
|
||||
window.location.href = `/shop/products.html?search=${encodeURIComponent(this.searchQuery)}`;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Go to cart page
|
||||
*/
|
||||
goToCart() {
|
||||
window.location.href = '/shop/cart.html';
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// SHOP ACCOUNT LAYOUT COMPONENT
|
||||
// Layout for customer account area (orders, profile, addresses)
|
||||
// =============================================================================
|
||||
|
||||
window.shopAccountLayout = function() {
|
||||
return {
|
||||
...window.shopLayout(),
|
||||
|
||||
// Account-specific state
|
||||
customer: null,
|
||||
currentPage: '',
|
||||
|
||||
/**
|
||||
* Initialize shop account layout
|
||||
*/
|
||||
async init() {
|
||||
this.currentPage = this.getCurrentPage();
|
||||
this.sessionId = this.getOrCreateSessionId();
|
||||
await this.detectVendor();
|
||||
await this.loadCustomerData();
|
||||
if (this.vendor) {
|
||||
await this.loadCart();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Load customer data
|
||||
*/
|
||||
async loadCustomerData() {
|
||||
if (!this.vendor) return;
|
||||
|
||||
try {
|
||||
const response = await apiClient.get(
|
||||
`/public/vendors/${this.vendor.id}/customers/me`
|
||||
);
|
||||
this.customer = response;
|
||||
} catch (error) {
|
||||
console.error('Failed to load customer data:', error);
|
||||
// Redirect to login if not authenticated
|
||||
if (error.status === 401) {
|
||||
window.location.href = `/shop/account/login.html?redirect=${encodeURIComponent(window.location.pathname)}`;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current page name from URL
|
||||
*/
|
||||
getCurrentPage() {
|
||||
const path = window.location.pathname;
|
||||
const page = path.split('/').pop().replace('.html', '');
|
||||
return page || 'orders';
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if menu item is active
|
||||
*/
|
||||
isActive(page) {
|
||||
return this.currentPage === page;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show logout confirmation
|
||||
*/
|
||||
confirmLogout() {
|
||||
this.showConfirmModal({
|
||||
title: 'Confirm Logout',
|
||||
message: 'Are you sure you want to logout?',
|
||||
buttonText: 'Logout',
|
||||
buttonClass: 'btn-primary',
|
||||
onConfirm: () => this.logoutCustomer()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform customer logout
|
||||
*/
|
||||
async logoutCustomer() {
|
||||
if (!this.vendor) return;
|
||||
|
||||
try {
|
||||
this.showLoading();
|
||||
await apiClient.post(`/public/vendors/${this.vendor.id}/customers/logout`);
|
||||
window.location.href = '/shop/home.html';
|
||||
} catch (error) {
|
||||
this.hideLoading();
|
||||
this.showErrorModal({
|
||||
message: 'Logout failed',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -338,3 +338,40 @@ document.head.appendChild(style);
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { APIClient, apiClient, Auth, Utils };
|
||||
}
|
||||
|
||||
// Table scroll detection helper
|
||||
function initTableScrollDetection() {
|
||||
const observer = new MutationObserver(() => {
|
||||
const tables = document.querySelectorAll('.table-responsive');
|
||||
tables.forEach(table => {
|
||||
if (!table.hasAttribute('data-scroll-initialized')) {
|
||||
table.setAttribute('data-scroll-initialized', 'true');
|
||||
|
||||
table.addEventListener('scroll', function() {
|
||||
if (this.scrollLeft > 0) {
|
||||
this.classList.add('is-scrolled');
|
||||
} else {
|
||||
this.classList.remove('is-scrolled');
|
||||
}
|
||||
});
|
||||
|
||||
// Check initial state
|
||||
if (table.scrollLeft > 0) {
|
||||
table.classList.add('is-scrolled');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initTableScrollDetection);
|
||||
} else {
|
||||
initTableScrollDetection();
|
||||
}
|
||||
209
static/js/shared/modal-system.js
Normal file
209
static/js/shared/modal-system.js
Normal file
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* Modal System Helper Functions
|
||||
* Utility functions for modal operations across all sections
|
||||
*/
|
||||
|
||||
window.modalHelpers = {
|
||||
/**
|
||||
* Show a simple confirmation dialog
|
||||
* Returns a Promise that resolves with true/false
|
||||
*/
|
||||
async confirm(options) {
|
||||
return new Promise((resolve) => {
|
||||
const component = Alpine.$data(document.body);
|
||||
|
||||
component.showConfirmModal({
|
||||
title: options.title || 'Confirm Action',
|
||||
message: options.message || 'Are you sure?',
|
||||
warning: options.warning || '',
|
||||
buttonText: options.buttonText || 'Confirm',
|
||||
buttonClass: options.buttonClass || 'btn-danger',
|
||||
onConfirm: () => resolve(true),
|
||||
onCancel: () => resolve(false)
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a success message
|
||||
*/
|
||||
success(message, options = {}) {
|
||||
const component = Alpine.$data(document.body);
|
||||
|
||||
component.showSuccessModal({
|
||||
title: options.title || 'Success',
|
||||
message: message,
|
||||
redirectUrl: options.redirectUrl || null,
|
||||
redirectDelay: options.redirectDelay || 2000
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show an error message
|
||||
*/
|
||||
error(message, details = '') {
|
||||
const component = Alpine.$data(document.body);
|
||||
|
||||
component.showErrorModal({
|
||||
title: 'Error',
|
||||
message: message,
|
||||
details: details
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show API error with proper formatting
|
||||
*/
|
||||
apiError(error) {
|
||||
const component = Alpine.$data(document.body);
|
||||
|
||||
let message = 'An error occurred';
|
||||
let details = '';
|
||||
|
||||
if (error.message) {
|
||||
message = error.message;
|
||||
}
|
||||
|
||||
if (error.details) {
|
||||
details = typeof error.details === 'string'
|
||||
? error.details
|
||||
: JSON.stringify(error.details, null, 2);
|
||||
} else if (error.error_code) {
|
||||
details = `Error Code: ${error.error_code}`;
|
||||
}
|
||||
|
||||
component.showErrorModal({
|
||||
title: 'Error',
|
||||
message: message,
|
||||
details: details
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show loading overlay
|
||||
*/
|
||||
showLoading() {
|
||||
const component = Alpine.$data(document.body);
|
||||
component.showLoading();
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide loading overlay
|
||||
*/
|
||||
hideLoading() {
|
||||
const component = Alpine.$data(document.body);
|
||||
component.hideLoading();
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute an async operation with loading state
|
||||
*/
|
||||
async withLoading(asyncFunction) {
|
||||
try {
|
||||
this.showLoading();
|
||||
const result = await asyncFunction();
|
||||
return result;
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute an async operation with error handling
|
||||
*/
|
||||
async withErrorHandling(asyncFunction, errorMessage = 'Operation failed') {
|
||||
try {
|
||||
return await asyncFunction();
|
||||
} catch (error) {
|
||||
console.error('Operation error:', error);
|
||||
this.apiError({
|
||||
message: errorMessage,
|
||||
details: error.message || error.toString()
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute an async operation with both loading and error handling
|
||||
*/
|
||||
async execute(asyncFunction, options = {}) {
|
||||
const {
|
||||
errorMessage = 'Operation failed',
|
||||
successMessage = null,
|
||||
redirectUrl = null
|
||||
} = options;
|
||||
|
||||
try {
|
||||
this.showLoading();
|
||||
const result = await asyncFunction();
|
||||
|
||||
if (successMessage) {
|
||||
this.success(successMessage, { redirectUrl });
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Operation error:', error);
|
||||
this.apiError({
|
||||
message: errorMessage,
|
||||
details: error.message || error.toString()
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm a destructive action
|
||||
*/
|
||||
async confirmDelete(itemName, itemType = 'item') {
|
||||
return this.confirm({
|
||||
title: `Delete ${itemType}`,
|
||||
message: `Are you sure you want to delete "${itemName}"?`,
|
||||
warning: 'This action cannot be undone.',
|
||||
buttonText: 'Delete',
|
||||
buttonClass: 'btn-danger'
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm logout
|
||||
*/
|
||||
async confirmLogout() {
|
||||
return this.confirm({
|
||||
title: 'Confirm Logout',
|
||||
message: 'Are you sure you want to logout?',
|
||||
buttonText: 'Logout',
|
||||
buttonClass: 'btn-primary'
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show validation errors
|
||||
*/
|
||||
validationError(errors) {
|
||||
let message = 'Please correct the following errors:';
|
||||
let details = '';
|
||||
|
||||
if (Array.isArray(errors)) {
|
||||
details = errors.join('\n');
|
||||
} else if (typeof errors === 'object') {
|
||||
details = Object.entries(errors)
|
||||
.map(([field, error]) => `${field}: ${error}`)
|
||||
.join('\n');
|
||||
} else {
|
||||
details = errors.toString();
|
||||
}
|
||||
|
||||
this.error(message, details);
|
||||
}
|
||||
};
|
||||
|
||||
// Shorthand aliases for convenience
|
||||
window.showConfirm = window.modalHelpers.confirm.bind(window.modalHelpers);
|
||||
window.showSuccess = window.modalHelpers.success.bind(window.modalHelpers);
|
||||
window.showError = window.modalHelpers.error.bind(window.modalHelpers);
|
||||
window.showLoading = window.modalHelpers.showLoading.bind(window.modalHelpers);
|
||||
window.hideLoading = window.modalHelpers.hideLoading.bind(window.modalHelpers);
|
||||
114
static/js/shared/modal-templates.js
Normal file
114
static/js/shared/modal-templates.js
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Universal Modal Templates
|
||||
* Shared across all sections: Admin, Vendor, and Shop
|
||||
*/
|
||||
|
||||
window.modalTemplates = {
|
||||
|
||||
/**
|
||||
* Confirmation Modal
|
||||
*/
|
||||
confirmModal: () => `
|
||||
<div x-show="confirmModal.show"
|
||||
x-cloak
|
||||
class="modal-backdrop"
|
||||
@click.self="closeConfirmModal()">
|
||||
<div class="modal-container" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" x-text="confirmModal.title"></h3>
|
||||
<button @click="closeConfirmModal()" class="modal-close">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="modal-message" x-text="confirmModal.message"></p>
|
||||
<div x-show="confirmModal.warning" class="modal-warning">
|
||||
<p x-text="confirmModal.warning"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button @click="closeConfirmModal()" class="btn btn-outline">
|
||||
Cancel
|
||||
</button>
|
||||
<button @click="handleConfirm()"
|
||||
class="btn"
|
||||
:class="confirmModal.buttonClass"
|
||||
x-text="confirmModal.buttonText">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
/**
|
||||
* Success Modal
|
||||
*/
|
||||
successModal: () => `
|
||||
<div x-show="successModal.show"
|
||||
x-cloak
|
||||
class="modal-backdrop"
|
||||
@click.self="closeSuccessModal()">
|
||||
<div class="modal-container" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" x-text="successModal.title"></h3>
|
||||
<button @click="closeSuccessModal()" class="modal-close">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-icon success">
|
||||
<i class="fas fa-check"></i>
|
||||
</div>
|
||||
<p class="modal-message text-center" x-text="successModal.message"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button @click="closeSuccessModal()" class="btn btn-primary">
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
/**
|
||||
* Error Modal
|
||||
*/
|
||||
errorModal: () => `
|
||||
<div x-show="errorModal.show"
|
||||
x-cloak
|
||||
class="modal-backdrop"
|
||||
@click.self="closeErrorModal()">
|
||||
<div class="modal-container" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" x-text="errorModal.title"></h3>
|
||||
<button @click="closeErrorModal()" class="modal-close">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-icon error">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</div>
|
||||
<p class="modal-message text-center" x-text="errorModal.message"></p>
|
||||
<div x-show="errorModal.details" class="modal-details" x-text="errorModal.details"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button @click="closeErrorModal()" class="btn btn-primary">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
/**
|
||||
* Loading Overlay
|
||||
*/
|
||||
loadingOverlay: () => `
|
||||
<div x-show="loading"
|
||||
x-cloak
|
||||
class="loading-overlay">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
Reference in New Issue
Block a user