377 lines
9.2 KiB
JavaScript
377 lines
9.2 KiB
JavaScript
// static/js/shared/api-client.js
|
|
/**
|
|
* API Client for Multi-Tenant Ecommerce Platform
|
|
*
|
|
* Provides utilities for:
|
|
* - Making authenticated API calls
|
|
* - Token management
|
|
* - Error handling
|
|
* - Request/response interceptors
|
|
*/
|
|
|
|
const API_BASE_URL = '/api/v1';
|
|
|
|
/**
|
|
* API Client Class
|
|
*/
|
|
class APIClient {
|
|
constructor(baseURL = API_BASE_URL) {
|
|
this.baseURL = baseURL;
|
|
}
|
|
|
|
/**
|
|
* Get stored authentication token
|
|
*/
|
|
getToken() {
|
|
return localStorage.getItem('admin_token') || localStorage.getItem('vendor_token');
|
|
}
|
|
|
|
/**
|
|
* Get default headers with authentication
|
|
*/
|
|
getHeaders(additionalHeaders = {}) {
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
...additionalHeaders
|
|
};
|
|
|
|
const token = this.getToken();
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
/**
|
|
* Make API request
|
|
*/
|
|
async request(endpoint, options = {}) {
|
|
const url = `${this.baseURL}${endpoint}`;
|
|
|
|
const config = {
|
|
...options,
|
|
headers: this.getHeaders(options.headers)
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(url, config);
|
|
|
|
// Handle 401 Unauthorized
|
|
if (response.status === 401) {
|
|
this.handleUnauthorized();
|
|
throw new Error('Unauthorized - please login again');
|
|
}
|
|
|
|
// Parse response
|
|
const data = await response.json();
|
|
|
|
// Handle non-OK responses
|
|
if (!response.ok) {
|
|
throw new Error(data.detail || data.message || 'Request failed');
|
|
}
|
|
|
|
return data;
|
|
|
|
} catch (error) {
|
|
console.error('API request failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET request
|
|
*/
|
|
async get(endpoint, params = {}) {
|
|
const queryString = new URLSearchParams(params).toString();
|
|
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
|
|
|
|
return this.request(url, {
|
|
method: 'GET'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* POST request
|
|
*/
|
|
async post(endpoint, data = {}) {
|
|
return this.request(endpoint, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* PUT request
|
|
*/
|
|
async put(endpoint, data = {}) {
|
|
return this.request(endpoint, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* DELETE request
|
|
*/
|
|
async delete(endpoint) {
|
|
return this.request(endpoint, {
|
|
method: 'DELETE'
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle unauthorized access
|
|
*/
|
|
handleUnauthorized() {
|
|
localStorage.removeItem('admin_token');
|
|
localStorage.removeItem('admin_user');
|
|
localStorage.removeItem('vendor_token');
|
|
localStorage.removeItem('vendor_user');
|
|
|
|
// Redirect to appropriate login page
|
|
if (window.location.pathname.includes('/admin/')) {
|
|
window.location.href = '/static/admin/login.html';
|
|
} else if (window.location.pathname.includes('/vendor/')) {
|
|
window.location.href = '/static/vendor/login.html';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create global API client instance
|
|
const apiClient = new APIClient();
|
|
|
|
/**
|
|
* Authentication helpers
|
|
*/
|
|
const Auth = {
|
|
/**
|
|
* Check if user is authenticated
|
|
*/
|
|
isAuthenticated() {
|
|
const token = localStorage.getItem('admin_token') || localStorage.getItem('vendor_token');
|
|
return !!token;
|
|
},
|
|
|
|
/**
|
|
* Get current user
|
|
*/
|
|
getCurrentUser() {
|
|
const userStr = localStorage.getItem('admin_user') || localStorage.getItem('vendor_user');
|
|
if (!userStr) return null;
|
|
|
|
try {
|
|
return JSON.parse(userStr);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Check if user is admin
|
|
*/
|
|
isAdmin() {
|
|
const user = this.getCurrentUser();
|
|
return user && user.role === 'admin';
|
|
},
|
|
|
|
/**
|
|
* Login
|
|
*/
|
|
async login(username, password) {
|
|
const response = await apiClient.post('/auth/login', {
|
|
username,
|
|
password
|
|
});
|
|
|
|
// Store token and user
|
|
if (response.user.role === 'admin') {
|
|
localStorage.setItem('admin_token', response.access_token);
|
|
localStorage.setItem('admin_user', JSON.stringify(response.user));
|
|
} else {
|
|
localStorage.setItem('vendor_token', response.access_token);
|
|
localStorage.setItem('vendor_user', JSON.stringify(response.user));
|
|
}
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* Logout
|
|
*/
|
|
logout() {
|
|
localStorage.removeItem('admin_token');
|
|
localStorage.removeItem('admin_user');
|
|
localStorage.removeItem('vendor_token');
|
|
localStorage.removeItem('vendor_user');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Utility functions
|
|
*/
|
|
const Utils = {
|
|
/**
|
|
* Format date
|
|
*/
|
|
formatDate(dateString) {
|
|
if (!dateString) return '-';
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('en-GB', {
|
|
day: '2-digit',
|
|
month: 'short',
|
|
year: 'numeric'
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Format datetime
|
|
*/
|
|
formatDateTime(dateString) {
|
|
if (!dateString) return '-';
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString('en-GB', {
|
|
day: '2-digit',
|
|
month: 'short',
|
|
year: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Format currency
|
|
*/
|
|
formatCurrency(amount, currency = 'EUR') {
|
|
if (amount === null || amount === undefined) return '-';
|
|
return new Intl.NumberFormat('en-GB', {
|
|
style: 'currency',
|
|
currency: currency
|
|
}).format(amount);
|
|
},
|
|
|
|
/**
|
|
* Debounce function
|
|
*/
|
|
debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Show toast notification
|
|
*/
|
|
showToast(message, type = 'info', duration = 3000) {
|
|
// Create toast element
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
toast.textContent = message;
|
|
|
|
// Style
|
|
toast.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
padding: 16px 24px;
|
|
background: ${type === 'success' ? '#4caf50' : type === 'error' ? '#f44336' : '#2196f3'};
|
|
color: white;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
z-index: 10000;
|
|
animation: slideIn 0.3s ease;
|
|
max-width: 400px;
|
|
`;
|
|
|
|
// Add to page
|
|
document.body.appendChild(toast);
|
|
|
|
// Remove after duration
|
|
setTimeout(() => {
|
|
toast.style.animation = 'slideOut 0.3s ease';
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, duration);
|
|
},
|
|
|
|
/**
|
|
* Confirm dialog
|
|
*/
|
|
async confirm(message, title = 'Confirm') {
|
|
return window.confirm(`${title}\n\n${message}`);
|
|
}
|
|
};
|
|
|
|
// Add animation styles
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
@keyframes slideIn {
|
|
from {
|
|
transform: translateX(400px);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@keyframes slideOut {
|
|
from {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
transform: translateX(400px);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
// Export for use in other scripts
|
|
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();
|
|
} |