admin login migration to new structure, new design
This commit is contained in:
377
static/shared/js/api-client.js
Normal file
377
static/shared/js/api-client.js
Normal file
@@ -0,0 +1,377 @@
|
||||
// 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();
|
||||
}
|
||||
Reference in New Issue
Block a user