438 lines
14 KiB
JavaScript
438 lines
14 KiB
JavaScript
/* ===== FURRY AJAX LIBRARY ===== */
|
||
/* Moderne AJAX-Funktionen für kasico.de */
|
||
|
||
class FurryAjax {
|
||
constructor() {
|
||
this.csrfToken = this.getCSRFToken();
|
||
this.baseURL = window.location.origin;
|
||
this.setupGlobalHandlers();
|
||
}
|
||
|
||
// CSRF Token aus Meta-Tag oder Cookie holen
|
||
getCSRFToken() {
|
||
const metaTag = document.querySelector('meta[name=csrf-token]');
|
||
if (metaTag) return metaTag.getAttribute('content');
|
||
|
||
const cookie = document.cookie.match(/csrftoken=([^;]+)/);
|
||
return cookie ? cookie[1] : '';
|
||
}
|
||
|
||
// Globale Event-Handler einrichten
|
||
setupGlobalHandlers() {
|
||
// Loading States für alle AJAX-Requests
|
||
document.addEventListener('furry-ajax-start', this.showLoading.bind(this));
|
||
document.addEventListener('furry-ajax-end', this.hideLoading.bind(this));
|
||
|
||
// Error Handler
|
||
document.addEventListener('furry-ajax-error', this.handleError.bind(this));
|
||
}
|
||
|
||
// Haupt-AJAX-Funktion
|
||
async request(url, options = {}) {
|
||
const defaultOptions = {
|
||
method: 'GET',
|
||
headers: {
|
||
'X-CSRFToken': this.csrfToken,
|
||
'Content-Type': 'application/json',
|
||
'X-Requested-With': 'XMLHttpRequest'
|
||
},
|
||
credentials: 'same-origin'
|
||
};
|
||
|
||
const config = { ...defaultOptions, ...options };
|
||
|
||
// Loading State triggern
|
||
this.triggerEvent('furry-ajax-start');
|
||
|
||
try {
|
||
const response = await fetch(url, config);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const contentType = response.headers.get('content-type');
|
||
let data;
|
||
|
||
if (contentType && contentType.includes('application/json')) {
|
||
data = await response.json();
|
||
} else {
|
||
data = await response.text();
|
||
}
|
||
|
||
this.triggerEvent('furry-ajax-end');
|
||
return { success: true, data, response };
|
||
|
||
} catch (error) {
|
||
this.triggerEvent('furry-ajax-error', { error });
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
// Warenkorb-Funktionen
|
||
async addToCart(productId, quantity = 1) {
|
||
const result = await this.request(`/add-to-cart/${productId}/`, {
|
||
method: 'POST',
|
||
body: JSON.stringify({ quantity })
|
||
});
|
||
|
||
if (result.success) {
|
||
this.showSuccess('Produkt wurde zum Warenkorb hinzugefügt!');
|
||
this.updateCartCount(result.data.cart_count);
|
||
} else {
|
||
this.showError('Fehler beim Hinzufügen zum Warenkorb');
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
async updateCartItem(itemId, quantity) {
|
||
const result = await this.request(`/update-cart-item/${itemId}/`, {
|
||
method: 'POST',
|
||
body: JSON.stringify({ quantity })
|
||
});
|
||
|
||
if (result.success) {
|
||
this.updateCartTotal(result.data.total);
|
||
this.showSuccess('Warenkorb aktualisiert!');
|
||
} else {
|
||
this.showError('Fehler beim Aktualisieren des Warenkorbs');
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
async removeFromCart(itemId) {
|
||
const result = await this.request(`/remove-cart-item/${itemId}/`, {
|
||
method: 'POST'
|
||
});
|
||
|
||
if (result.success) {
|
||
this.showSuccess('Produkt aus Warenkorb entfernt!');
|
||
this.updateCartCount(result.data.cart_count);
|
||
// Element aus DOM entfernen
|
||
const cartItem = document.querySelector(`[data-cart-item="${itemId}"]`);
|
||
if (cartItem) {
|
||
cartItem.style.animation = 'fadeOut 0.3s ease';
|
||
setTimeout(() => cartItem.remove(), 300);
|
||
}
|
||
} else {
|
||
this.showError('Fehler beim Entfernen aus Warenkorb');
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// Wunschliste-Funktionen
|
||
async addToWishlist(productId) {
|
||
const result = await this.request(`/add-to-wishlist/${productId}/`, {
|
||
method: 'POST'
|
||
});
|
||
|
||
if (result.success) {
|
||
this.showSuccess('Produkt zur Wunschliste hinzugefügt!');
|
||
this.updateWishlistCount(result.data.wishlist_count);
|
||
} else {
|
||
this.showError('Fehler beim Hinzufügen zur Wunschliste');
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
async removeFromWishlist(productId) {
|
||
const result = await this.request(`/remove-from-wishlist/${productId}/`, {
|
||
method: 'POST'
|
||
});
|
||
|
||
if (result.success) {
|
||
this.showSuccess('Produkt aus Wunschliste entfernt!');
|
||
this.updateWishlistCount(result.data.wishlist_count);
|
||
} else {
|
||
this.showError('Fehler beim Entfernen aus Wunschliste');
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// Bewertungen
|
||
async submitReview(productId, rating, comment) {
|
||
const result = await this.request(`/submit-review/${productId}/`, {
|
||
method: 'POST',
|
||
body: JSON.stringify({ rating, comment })
|
||
});
|
||
|
||
if (result.success) {
|
||
this.showSuccess('Bewertung erfolgreich abgegeben!');
|
||
this.updateProductRating(productId, result.data.average_rating);
|
||
} else {
|
||
this.showError('Fehler beim Abgeben der Bewertung');
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// Live-Suche
|
||
async searchProducts(query) {
|
||
const result = await this.request(`/search-products/?q=${encodeURIComponent(query)}`);
|
||
|
||
if (result.success) {
|
||
this.updateSearchResults(result.data.products);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// Filter-Funktionen
|
||
async applyFilters(filters) {
|
||
const queryString = new URLSearchParams(filters).toString();
|
||
const result = await this.request(`/filter-products/?${queryString}`);
|
||
|
||
if (result.success) {
|
||
this.updateProductGrid(result.data.products);
|
||
this.updateFilterCounts(result.data.counts);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// Loading States
|
||
showLoading(element = null) {
|
||
const target = element || document.body;
|
||
target.classList.add('loading');
|
||
|
||
// Loading Spinner hinzufügen
|
||
const spinner = document.createElement('div');
|
||
spinner.className = 'furry-loading furry-loading-lg';
|
||
spinner.id = 'furry-loading-spinner';
|
||
target.appendChild(spinner);
|
||
}
|
||
|
||
hideLoading(element = null) {
|
||
const target = element || document.body;
|
||
target.classList.remove('loading');
|
||
|
||
// Loading Spinner entfernen
|
||
const spinner = document.getElementById('furry-loading-spinner');
|
||
if (spinner) spinner.remove();
|
||
}
|
||
|
||
// Benachrichtigungen
|
||
showSuccess(message, duration = 3000) {
|
||
this.showNotification(message, 'success', duration);
|
||
}
|
||
|
||
showError(message, duration = 5000) {
|
||
this.showNotification(message, 'error', duration);
|
||
}
|
||
|
||
showWarning(message, duration = 4000) {
|
||
this.showNotification(message, 'warning', duration);
|
||
}
|
||
|
||
showInfo(message, duration = 3000) {
|
||
this.showNotification(message, 'info', duration);
|
||
}
|
||
|
||
showNotification(message, type = 'info', duration = 3000) {
|
||
const alert = document.createElement('div');
|
||
alert.className = `furry-alert furry-alert-${type}`;
|
||
alert.style.position = 'fixed';
|
||
alert.style.top = '20px';
|
||
alert.style.right = '20px';
|
||
alert.style.zIndex = '9999';
|
||
alert.style.maxWidth = '400px';
|
||
alert.style.animation = 'slideInRight 0.3s ease';
|
||
|
||
alert.innerHTML = `
|
||
<div class="furry-alert-icon">
|
||
${type === 'success' ? '✅' :
|
||
type === 'error' ? '❌' :
|
||
type === 'warning' ? '⚠️' : 'ℹ️'}
|
||
</div>
|
||
<div class="furry-alert-content">
|
||
<div class="furry-alert-title">${type.charAt(0).toUpperCase() + type.slice(1)}</div>
|
||
<div class="furry-alert-message">${message}</div>
|
||
</div>
|
||
<button onclick="this.parentElement.remove()" style="background: none; border: none; color: inherit; font-size: 1.2rem; cursor: pointer; margin-left: 0.5rem;">×</button>
|
||
`;
|
||
|
||
document.body.appendChild(alert);
|
||
|
||
// Auto-remove nach duration
|
||
setTimeout(() => {
|
||
if (alert.parentElement) {
|
||
alert.style.animation = 'slideOutRight 0.3s ease';
|
||
setTimeout(() => alert.remove(), 300);
|
||
}
|
||
}, duration);
|
||
}
|
||
|
||
// DOM Updates
|
||
updateCartCount(count) {
|
||
const cartBadge = document.querySelector('.cart-count');
|
||
if (cartBadge) {
|
||
cartBadge.textContent = count;
|
||
cartBadge.style.animation = 'bounce 0.5s ease';
|
||
}
|
||
}
|
||
|
||
updateWishlistCount(count) {
|
||
const wishlistBadge = document.querySelector('.wishlist-count');
|
||
if (wishlistBadge) {
|
||
wishlistBadge.textContent = count;
|
||
wishlistBadge.style.animation = 'bounce 0.5s ease';
|
||
}
|
||
}
|
||
|
||
updateCartTotal(total) {
|
||
const totalElement = document.querySelector('.cart-total');
|
||
if (totalElement) {
|
||
totalElement.textContent = `${total}€`;
|
||
totalElement.style.animation = 'pulse 0.5s ease';
|
||
}
|
||
}
|
||
|
||
updateProductRating(productId, averageRating) {
|
||
const ratingElement = document.querySelector(`[data-product-rating="${productId}"]`);
|
||
if (ratingElement) {
|
||
ratingElement.innerHTML = this.generateStars(averageRating);
|
||
ratingElement.style.animation = 'fadeIn 0.5s ease';
|
||
}
|
||
}
|
||
|
||
updateSearchResults(products) {
|
||
const resultsContainer = document.querySelector('.search-results');
|
||
if (resultsContainer && products) {
|
||
resultsContainer.innerHTML = this.generateProductHTML(products);
|
||
resultsContainer.style.animation = 'fadeIn 0.3s ease';
|
||
}
|
||
}
|
||
|
||
updateProductGrid(products) {
|
||
const gridContainer = document.querySelector('.products-grid');
|
||
if (gridContainer && products) {
|
||
gridContainer.innerHTML = this.generateProductHTML(products);
|
||
gridContainer.style.animation = 'fadeIn 0.3s ease';
|
||
}
|
||
}
|
||
|
||
updateFilterCounts(counts) {
|
||
Object.keys(counts).forEach(filterType => {
|
||
const countElement = document.querySelector(`[data-filter-count="${filterType}"]`);
|
||
if (countElement) {
|
||
countElement.textContent = counts[filterType];
|
||
}
|
||
});
|
||
}
|
||
|
||
// Helper Functions
|
||
generateStars(rating) {
|
||
let stars = '';
|
||
for (let i = 1; i <= 5; i++) {
|
||
if (i <= rating) {
|
||
stars += '⭐';
|
||
} else {
|
||
stars += '☆';
|
||
}
|
||
}
|
||
return stars;
|
||
}
|
||
|
||
generateProductHTML(products) {
|
||
return products.map(product => `
|
||
<div class="furry-card furry-product-card">
|
||
${product.image ?
|
||
`<img src="${product.image}" alt="${product.name}" class="furry-card-image">` :
|
||
`<div class="furry-card-image" style="background: linear-gradient(45deg, var(--furry-primary), var(--furry-secondary)); display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem;">🐾</div>`
|
||
}
|
||
<div class="furry-card-header">
|
||
<div>
|
||
<h3 class="furry-card-title">${product.name}</h3>
|
||
<p class="furry-card-subtitle">${product.fursuit_type} • ${product.style}</p>
|
||
</div>
|
||
${product.featured ? '<div class="furry-product-badge">Featured</div>' : ''}
|
||
</div>
|
||
<div class="furry-card-content">
|
||
<p>${product.description}</p>
|
||
<div class="furry-product-price">${product.price}€</div>
|
||
</div>
|
||
<div class="furry-card-footer">
|
||
<a href="/product/${product.id}/" class="furry-btn furry-btn-primary furry-btn-sm">👁️ Details</a>
|
||
<button onclick="furryAjax.addToCart(${product.id})" class="furry-btn furry-btn-outline furry-btn-sm">🛒 Hinzufügen</button>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
// Event Handling
|
||
triggerEvent(eventName, data = {}) {
|
||
const event = new CustomEvent(eventName, { detail: data });
|
||
document.dispatchEvent(event);
|
||
}
|
||
|
||
handleError(event) {
|
||
console.error('FurryAjax Error:', event.detail.error);
|
||
this.showError('Ein Fehler ist aufgetreten. Bitte versuche es erneut.');
|
||
}
|
||
}
|
||
|
||
// Globale Instanz erstellen
|
||
const furryAjax = new FurryAjax();
|
||
|
||
// CSS für Animationen
|
||
const style = document.createElement('style');
|
||
style.textContent = `
|
||
@keyframes slideInRight {
|
||
from { transform: translateX(100%); opacity: 0; }
|
||
to { transform: translateX(0); opacity: 1; }
|
||
}
|
||
|
||
@keyframes slideOutRight {
|
||
from { transform: translateX(0); opacity: 1; }
|
||
to { transform: translateX(100%); opacity: 0; }
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
@keyframes fadeOut {
|
||
from { opacity: 1; }
|
||
to { opacity: 0; }
|
||
}
|
||
|
||
@keyframes bounce {
|
||
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
|
||
40% { transform: translateY(-10px); }
|
||
60% { transform: translateY(-5px); }
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% { transform: scale(1); }
|
||
50% { transform: scale(1.05); }
|
||
100% { transform: scale(1); }
|
||
}
|
||
|
||
.loading {
|
||
position: relative;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.loading::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(255, 255, 255, 0.8);
|
||
z-index: 1000;
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
|
||
// Export für Module-System
|
||
if (typeof module !== 'undefined' && module.exports) {
|
||
module.exports = FurryAjax;
|
||
}
|