Files
orion/static/js/shared/alpine-components.js

581 lines
17 KiB
JavaScript

/**
* 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
});
}
}
};
};