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