{% block product_review_modal_inline %}
<!-- Inline CSS for Review Modal -->
<style>
.product-review-modal .star-rating {
display: flex;
gap: 4px;
margin-bottom: 0.5rem;
}
.product-review-modal .star-rating .star {
font-size: 1.5rem;
color: #ddd;
cursor: pointer;
transition: color 0.2s ease;
user-select: none;
}
.product-review-modal .star-rating .star:hover,
.product-review-modal .star-rating .star.active {
color: #ffc107;
}
.product-review-modal .modal-content {
border-radius: 0.75rem;
border: none;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
}
.product-review-modal .modal-header {
background: linear-gradient(135deg, var(--bs-primary), #0056b3);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
border-bottom: none;
}
.product-review-modal .modal-header .modal-title {
font-weight: 600;
font-size: 1.25rem;
text-transform: uppercase;
}
.product-review-modal .modal-header .btn-close {
filter: invert(1);
opacity: 0.8;
}
.product-review-modal .modal-header .btn-close:hover {
opacity: 1;
}
.product-review-modal .modal-body {
padding: 2rem;
}
.product-review-modal .review-step {
min-height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
}
.product-review-modal .review-step.active {
animation: fadeIn 0.3s ease-in;
}
.product-review-modal .review-step h3,
.product-review-modal .review-step h6 {
font-weight: 600;
color: var(--bs-dark);
text-transform: uppercase;
}
.product-review-modal #review-step-satisfaction {
text-align: center;
}
.product-review-modal #review-step-satisfaction .btn {
min-width: 150px;
font-weight: 500;
border-radius: 2rem;
padding: 0.75rem 2rem;
transition: all 0.3s ease;
}
.product-review-modal #review-step-satisfaction .btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.product-review-modal .form-label {
font-weight: 600;
color: var(--bs-dark);
margin-bottom: 0.5rem;
}
.product-review-modal .form-control {
border-radius: 0.5rem;
border: 2px solid #e9ecef;
transition: border-color 0.3s ease;
}
.product-review-modal .form-control:focus {
border-color: var(--bs-primary);
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
.product-review-modal .form-control.is-valid {
border-color: var(--bs-success);
}
.product-review-modal .form-control.is-invalid {
border-color: var(--bs-danger);
}
.product-review-modal #review-step-success,
.product-review-modal #review-step-error {
text-align: center;
min-height: 250px;
}
.product-review-modal .review-footer {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.product-review-modal #footer-satisfaction {
justify-content: center;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 768px) {
.product-review-modal .modal-body {
padding: 1.5rem;
}
.product-review-modal #review-step-satisfaction .btn {
width: 100%;
margin-bottom: 0.5rem;
}
.product-review-modal .star-rating {
justify-content: center;
}
.product-review-modal .star-rating .star {
font-size: 2rem;
}
}
</style>
<div class="modal fade product-review-modal" id="productReviewModal" tabindex="-1" aria-labelledby="productReviewModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="productReviewModalLabel">
{{ "review.modal.title"|trans|sw_sanitize }}
</h5>
</div>
<div class="modal-body">
<!-- Step 1: Satisfaction Question -->
<div id="review-step-satisfaction" class="review-step active">
<h3 class="mb-4 text-center">{{ "review.modal.satisfaction.title"|trans|sw_sanitize }}</h3>
<p class="text-muted mb-4 text-center">{{ "review.modal.satisfaction.subtitle"|trans|sw_sanitize }}</p>
<div class="d-flex justify-content-center gap-4">
<button type="button" class="btn btn-success btn-lg px-5" id="satisfaction-yes">
<i class="fas fa-thumbs-up me-2"></i>
{{ "review.modal.satisfaction.yes"|trans|sw_sanitize }}
</button>
<button type="button" class="btn btn-outline-danger btn-lg px-5" id="satisfaction-no">
<i class="fas fa-thumbs-down me-2"></i>
{{ "review.modal.satisfaction.no"|trans|sw_sanitize }}
</button>
</div>
</div>
<!-- Step 2: Review Form -->
<div id="review-step-form" class="review-step d-none">
<h6 class="mb-4">{{ "review.modal.form.title"|trans|sw_sanitize }}</h6>
<form id="reviewForm" novalidate>
<input type="hidden" id="productId" name="productId" value="{{ page.product.id }}">
<input type="hidden" id="productNumber" name="productNumber" value="{{ page.product.productNumber }}">
<!-- Star Rating -->
<div class="mb-4">
<label class="form-label">{{ "review.modal.form.rating"|trans|sw_sanitize }} *</label>
<div class="star-rating" id="starRating">
<span class="star" data-rating="1">★</span>
<span class="star" data-rating="2">★</span>
<span class="star" data-rating="3">★</span>
<span class="star" data-rating="4">★</span>
<span class="star" data-rating="5">★</span>
</div>
<input type="hidden" id="rating" name="rating" required>
<div class="invalid-feedback">{{ "review.modal.form.rating.required"|trans|sw_sanitize }}</div>
</div>
<!-- First Name and Last Name in one row -->
<div class="row mb-3">
<div class="col-md-6">
<label for="reviewerFirstName" class="form-label">{{ "review.modal.form.firstName"|trans|sw_sanitize }} *</label>
<input type="text" class="form-control" id="reviewerFirstName" name="reviewerFirstName" required maxlength="50">
<div class="invalid-feedback">{{ "review.modal.form.firstName.required"|trans|sw_sanitize }}</div>
</div>
<div class="col-md-6">
<label for="reviewerLastName" class="form-label">{{ "review.modal.form.lastName"|trans|sw_sanitize }} *</label>
<input type="text" class="form-control" id="reviewerLastName" name="reviewerLastName" required maxlength="50">
<div class="invalid-feedback">{{ "review.modal.form.lastName.required"|trans|sw_sanitize }}</div>
</div>
</div>
<!-- Email -->
<div class="mb-3">
<label for="reviewerEmail" class="form-label">{{ "review.modal.form.email"|trans|sw_sanitize }} *</label>
<input type="email" class="form-control" id="reviewerEmail" name="reviewerEmail" required>
<div class="invalid-feedback">{{ "review.modal.form.email.required"|trans|sw_sanitize }}</div>
</div>
<!-- Order Number -->
<div class="mb-3">
<label for="orderNumber" class="form-label">{{ "review.modal.form.orderNumber"|trans|sw_sanitize }} *</label>
<input type="text" class="form-control" id="orderNumber" name="orderNumber" required>
<div class="invalid-feedback">{{ "review.modal.form.orderNumber.required"|trans|sw_sanitize }}</div>
</div>
<!-- Comment -->
<div class="mb-4">
<label for="reviewComment" class="form-label">{{ "review.modal.form.comment"|trans|sw_sanitize }}</label>
<textarea class="form-control" id="reviewComment" name="reviewComment" rows="4" maxlength="500" placeholder="{{ "review.modal.form.comment.placeholder"|trans|sw_sanitize }}"></textarea>
<div class="form-text" id="commentCounter">0/500 Zeichen</div>
</div>
<!-- Privacy Notice -->
<div class="mb-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="privacyAccept" name="privacyAccept" required>
<label class="form-check-label" for="privacyAccept">
{{ "review.modal.form.privacy"|trans|sw_sanitize|raw }}
</label>
<div class="invalid-feedback">{{ "review.modal.form.privacy.required"|trans|sw_sanitize }}</div>
</div>
</div>
</form>
</div>
<!-- Success Message -->
<div id="review-step-success" class="review-step d-none text-center">
<div class="text-success mb-3">
<i class="fas fa-check-circle fa-3x"></i>
</div>
<h6 class="mb-3">{{ "review.modal.success.title"|trans|sw_sanitize }}</h6>
<p class="text-muted">{{ "review.modal.success.message"|trans|sw_sanitize }}</p>
</div>
<!-- Error Message -->
<div id="review-step-error" class="review-step d-none text-center">
<div class="text-danger mb-3">
<i class="fas fa-exclamation-circle fa-3x"></i>
</div>
<h6 class="mb-3">{{ "review.modal.error.title"|trans|sw_sanitize }}</h6>
<p class="text-muted" id="error-message">{{ "review.modal.error.message"|trans|sw_sanitize }}</p>
</div>
</div>
<div class="modal-footer">
<div id="footer-satisfaction" class="review-footer active">
<!-- No buttons needed for satisfaction step -->
</div>
<div id="footer-form" class="review-footer d-none">
<button type="button" class="btn btn-secondary" id="backToSatisfaction">
{{ "review.modal.button.back"|trans|sw_sanitize }}
</button>
<button type="button" class="btn btn-primary" id="submitReview">
<span class="submit-text">{{ "review.modal.button.submit"|trans|sw_sanitize }}</span>
<span class="spinner-border spinner-border-sm d-none" role="status">
<span class="visually-hidden">Loading...</span>
</span>
</button>
</div>
<div id="footer-success" class="review-footer d-none">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">
{{ "review.modal.button.close"|trans|sw_sanitize }}
</button>
</div>
<div id="footer-error" class="review-footer d-none">
<button type="button" class="btn btn-secondary" id="tryAgain">
{{ "review.modal.button.tryAgain"|trans|sw_sanitize }}
</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
{{ "review.modal.button.cancel"|trans|sw_sanitize }}
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Inline JavaScript for Review Modal -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const modal = document.querySelector('#productReviewModal');
const form = document.querySelector('#reviewForm');
let currentStep = 'satisfaction';
let selectedRating = 0;
// Event Listeners
const satisfactionYes = document.getElementById('satisfaction-yes');
const satisfactionNo = document.getElementById('satisfaction-no');
const backButton = document.getElementById('backToSatisfaction');
const submitButton = document.getElementById('submitReview');
const tryAgainButton = document.getElementById('tryAgain');
satisfactionYes?.addEventListener('click', () => showStep('form'));
satisfactionNo?.addEventListener('click', handleSatisfactionNo);
backButton?.addEventListener('click', () => showStep('satisfaction'));
submitButton?.addEventListener('click', handleSubmitReview);
tryAgainButton?.addEventListener('click', () => showStep('form'));
// Star Rating
const starContainer = document.querySelector('#starRating');
const stars = starContainer?.querySelectorAll('.star');
stars?.forEach((star, index) => {
star.addEventListener('click', () => setRating(index + 1));
star.addEventListener('mouseenter', () => highlightStars(index + 1));
});
starContainer?.addEventListener('mouseleave', () => highlightStars(selectedRating));
// Comment Counter
const commentTextarea = document.getElementById('reviewComment');
const counter = document.getElementById('commentCounter');
commentTextarea?.addEventListener('input', () => {
const currentLength = commentTextarea.value.length;
counter.textContent = `${currentLength}/500 Zeichen`;
});
// Form validation
form?.addEventListener('input', (e) => {
const input = e.target;
if (input.checkValidity()) {
input.classList.remove('is-invalid');
input.classList.add('is-valid');
} else {
input.classList.remove('is-valid');
}
});
// Preload Brevo widget when modal opens
modal?.addEventListener('shown.bs.modal', () => {
console.log('Modal opened, preloading Brevo widget');
// Only load if not already loaded
if (typeof window.BrevoConversations === 'undefined' && !window.brevoLoadingStarted) {
window.brevoLoadingStarted = true;
loadBrevoWidget();
}
});
// Modal reset
modal?.addEventListener('hidden.bs.modal', resetModal);
function setRating(rating) {
selectedRating = rating;
document.getElementById('rating').value = rating;
highlightStars(rating);
document.getElementById('rating').classList.remove('is-invalid');
}
function highlightStars(rating) {
stars?.forEach((star, index) => {
if (index < rating) {
star.classList.add('active');
} else {
star.classList.remove('active');
}
});
}
function showStep(stepName) {
document.querySelectorAll('.review-step').forEach(step => {
step.classList.remove('active');
step.classList.add('d-none');
});
document.querySelectorAll('.review-footer').forEach(footer => {
footer.classList.remove('active');
footer.classList.add('d-none');
});
const targetStep = document.getElementById(`review-step-${stepName}`);
const targetFooter = document.getElementById(`footer-${stepName}`);
if (targetStep) {
targetStep.classList.remove('d-none');
targetStep.classList.add('active');
}
if (targetFooter) {
targetFooter.classList.remove('d-none');
targetFooter.classList.add('active');
}
currentStep = stepName;
}
function handleSatisfactionNo() {
console.log('handleSatisfactionNo called');
// Close modal manually (Bootstrap 5 compatible)
try {
console.log('Closing modal');
modal.classList.remove('show');
modal.style.display = 'none';
modal.setAttribute('aria-hidden', 'true');
modal.removeAttribute('aria-modal');
modal.removeAttribute('role');
document.body.classList.remove('modal-open');
document.body.style.overflow = '';
document.body.style.paddingRight = '';
const backdrop = document.querySelector('.modal-backdrop');
if (backdrop) {
backdrop.remove();
}
} catch (error) {
console.error('Error closing modal:', error);
}
// Load and open Brevo chat (if not already loaded from modal opening)
setTimeout(() => {
if (typeof window.BrevoConversations !== 'undefined') {
console.log('Brevo already loaded, opening chat directly');
openBrevoChat();
} else {
console.log('Brevo not loaded yet, loading now');
loadBrevoWidget(() => openBrevoChat());
}
}, 400);
}
function loadBrevoWidget(callback) {
console.log('Loading Brevo Conversations widget...');
// Check if Brevo is already loaded
if (typeof window.BrevoConversations !== 'undefined') {
console.log('Brevo already loaded');
if (callback) callback();
return;
}
// Check if script is already loading
if (window.brevoLoadingStarted) {
console.log('Brevo already loading, waiting...');
// Wait for it to load and then call callback
const checkInterval = setInterval(() => {
if (typeof window.BrevoConversations !== 'undefined') {
clearInterval(checkInterval);
console.log('Brevo loaded (via waiting)');
if (callback) callback();
}
}, 200);
return;
}
window.brevoLoadingStarted = true;
// Load Brevo script dynamically
(function(d, w, c) {
w.BrevoConversationsID = '66755306756cb768ed6aa8bd';
w[c] = w[c] || function() {
(w[c].q = w[c].q || []).push(arguments);
};
var s = d.createElement('script');
s.async = true;
s.src = 'https://conversations-widget.brevo.com/brevo-conversations.js';
s.onload = function() {
console.log('Brevo script loaded successfully');
// Wait a bit for widget to initialize
setTimeout(() => {
if (callback) callback();
}, 500);
};
s.onerror = function() {
console.error('Failed to load Brevo script');
if (callback) {
// Redirect to contact page if we were trying to open chat
window.location.href = '/kontakt/';
}
};
if (d.head) d.head.appendChild(s);
})(document, window, 'BrevoConversations');
}
function openBrevoChat(attempt = 0) {
console.log('Attempting to open Brevo chat (attempt ' + (attempt + 1) + ')');
// Method 1: Try Brevo API first (most reliable for opening)
if (typeof window.BrevoConversations === 'function') {
console.log('Trying Brevo API');
try {
window.BrevoConversations('openChat', true);
console.log('Successfully called openChat');
// Wait longer for widget to fully open and initialize before sending message
setTimeout(() => {
try {
// Set the message in the input field
window.BrevoConversations('setUserMessage', 'Ich hatte ein Problem mit einem Produkt und benötige Unterstützung.');
console.log('Successfully set user message');
} catch (e) {
console.log('setUserMessage not available, trying alternative:', e);
// Alternative: try to find and fill the textarea directly
try {
const iframe = document.querySelector('iframe[title*="brevo" i]');
if (iframe && iframe.contentWindow) {
const textarea = iframe.contentWindow.document.querySelector('textarea');
if (textarea) {
textarea.value = 'Ich hatte ein Problem mit einem Produkt und benötige Unterstützung.';
console.log('Filled textarea directly');
}
}
} catch (iframeError) {
console.log('Cannot access iframe:', iframeError);
}
}
}, 1500);
return;
} catch (error) {
console.error('Brevo API error:', error);
}
}
// Method 2: Try to find and click the launcher button
const launcherSelectors = [
'.brevo-conversations-launcher',
'button[class*="brevo"]',
'button[class*="launcher"]',
'div[class*="brevo"][class*="launcher"]',
'[role="button"][class*="brevo"]'
];
for (const selector of launcherSelectors) {
const launcher = document.querySelector(selector);
if (launcher) {
console.log('Found launcher button:', selector, launcher);
launcher.click();
return;
}
}
// Method 3: Look for iframe and find launcher nearby
const iframe = document.querySelector('iframe[title*="brevo" i]') ||
document.querySelector('iframe[title*="chat" i]');
if (iframe) {
console.log('Found Brevo iframe, searching for launcher');
// Search in parent and siblings
const parent = iframe.parentElement;
if (parent) {
// Look for siblings that might be the launcher
const siblings = parent.parentElement?.children;
if (siblings) {
for (const sibling of siblings) {
if (sibling !== parent &&
(sibling.className.includes('launcher') ||
sibling.className.includes('button'))) {
console.log('Found launcher sibling:', sibling);
sibling.click();
return;
}
}
}
}
}
console.log('BrevoConversations type:', typeof window.BrevoConversations);
// Method 4: Retry up to 10 times (wait for Brevo to load and initialize)
if (attempt < 10) {
console.log('Brevo not ready, retrying in 500ms...');
setTimeout(() => openBrevoChat(attempt + 1), 500);
} else {
console.error('Failed to open Brevo chat after 10 attempts, redirecting to contact page');
window.location.href = '/kontakt/';
}
}
async function handleSubmitReview(e) {
e.preventDefault();
if (!validateForm()) {
return;
}
const submitText = submitButton.querySelector('.submit-text');
const spinner = submitButton.querySelector('.spinner-border');
submitButton.disabled = true;
submitText.classList.add('d-none');
spinner.classList.remove('d-none');
try {
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
// Combine first and last name into reviewerName field
const firstName = data.reviewerFirstName || '';
const lastName = data.reviewerLastName || '';
data.reviewerName = (firstName + ' ' + lastName).trim();
// Remove separate fields as they're not needed by the backend
delete data.reviewerFirstName;
delete data.reviewerLastName;
const response = await fetch('/product-review/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
showStep('success');
} else {
showError(result.message || 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
} catch (error) {
console.error('Review submission error:', error);
showError('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
} finally {
submitButton.disabled = false;
submitText.classList.remove('d-none');
spinner.classList.add('d-none');
}
}
function validateForm() {
let isValid = true;
if (selectedRating === 0) {
document.getElementById('rating').classList.add('is-invalid');
isValid = false;
}
const requiredFields = form?.querySelectorAll('[required]');
requiredFields?.forEach(field => {
if (!field.checkValidity()) {
field.classList.add('is-invalid');
isValid = false;
}
});
if (!isValid) {
const firstInvalid = form?.querySelector('.is-invalid');
firstInvalid?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
return isValid;
}
function showError(message) {
document.getElementById('error-message').textContent = message;
showStep('error');
}
function resetModal() {
form?.reset();
form?.querySelectorAll('.is-valid, .is-invalid').forEach(element => {
element.classList.remove('is-valid', 'is-invalid');
});
selectedRating = 0;
highlightStars(0);
document.getElementById('rating').value = '';
const counter = document.querySelector('.form-text');
if (counter) {
counter.textContent = '0/500 Zeichen';
}
showStep('satisfaction');
}
});
</script>
{% endblock %}