wineater-bot 3.8.2 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/wineater-chatbot.mjs +1549 -1452
- package/dist/wineater-chatbot.umd.js +105 -104
- package/package.json +1 -1
- package/src/js/analytics.js +53 -38
- package/src/js/api.js +1 -1
- package/src/js/i18n.js +9 -0
- package/src/js/widget/widget-events.js +18 -0
- package/src/js/widget/widget-language.js +107 -0
- package/src/js/widget/widget-search.js +68 -0
- package/src/js/widget/widget-ui.js +186 -0
- package/src/js/widget.js +191 -531
- /package/src/js/{widget-styles.js → widget/widget-styles.js} +0 -0
package/src/js/widget.js
CHANGED
|
@@ -1,567 +1,227 @@
|
|
|
1
1
|
// widget.js
|
|
2
|
-
import './widget-styles.js';
|
|
2
|
+
import './widget/widget-styles.js';
|
|
3
3
|
import { Api } from './api.js';
|
|
4
4
|
import translator from './i18n.js';
|
|
5
|
+
import { trackWidgetEvent } from './analytics.js';
|
|
6
|
+
import { WIDGET_EVENTS } from './widget/widget-events.js';
|
|
7
|
+
import { WidgetUI } from './widget/widget-ui.js';
|
|
8
|
+
import { WidgetSearch } from './widget/widget-search.js';
|
|
9
|
+
import { WidgetLanguage } from './widget/widget-language.js';
|
|
5
10
|
|
|
6
11
|
export class WineWidget {
|
|
7
12
|
constructor() {
|
|
8
|
-
//
|
|
9
|
-
this.widgetType = window?.wineaterData?.WIDGET_TYPE || 'store';
|
|
10
|
-
this.countryCode = window?.wineaterData?.COUNTRY_CODE || null;
|
|
11
|
-
this.widgetWrapper = window?.wineaterData?.WIDGET_WRAPPER || 'wineater-widget-conteiner';
|
|
13
|
+
// Widget configuration
|
|
14
|
+
this.widgetType = window?.wineaterData?.WIDGET_TYPE || 'store';
|
|
15
|
+
this.countryCode = window?.wineaterData?.COUNTRY_CODE || null;
|
|
16
|
+
this.widgetWrapper = window?.wineaterData?.WIDGET_WRAPPER || 'wineater-widget-conteiner';
|
|
12
17
|
this.titleText = window?.wineaterData?.TITLE_TEXT;
|
|
13
18
|
this.subtitleText = window?.wineaterData?.SUBTITLE_TEXT;
|
|
14
|
-
this.
|
|
15
|
-
console.log(this.titleText);
|
|
19
|
+
this.storeId = null;
|
|
16
20
|
|
|
17
|
-
//
|
|
21
|
+
// Initialize API
|
|
22
|
+
this.api = new Api();
|
|
23
|
+
|
|
24
|
+
// Initialize components
|
|
25
|
+
this.ui = new WidgetUI(this);
|
|
26
|
+
this.search = new WidgetSearch(this);
|
|
27
|
+
this.language = new WidgetLanguage(this);
|
|
28
|
+
|
|
29
|
+
// Filter configuration
|
|
18
30
|
this.selectedFilters = {
|
|
19
31
|
price: null,
|
|
20
32
|
color: null,
|
|
21
|
-
intensity: null
|
|
33
|
+
intensity: null
|
|
22
34
|
};
|
|
23
35
|
|
|
24
|
-
//
|
|
25
|
-
this.
|
|
26
|
-
this.initializeSearchAnimation();
|
|
27
|
-
this.sendEvent('widgetInitialized', { widgetType: this.widgetType });
|
|
28
|
-
this.loadInstantRecommendations();
|
|
29
|
-
this.createLanguageSwitcher();
|
|
36
|
+
// Initialize widget
|
|
37
|
+
this.initialized = this.initialize();
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
async trackEvent(eventType, data = {}, meta = {}) {
|
|
41
|
+
await this.initialized; // <-- ждём, пока storeId будет получен
|
|
42
|
+
trackWidgetEvent(eventType, this.storeId, data, meta);
|
|
43
|
+
}
|
|
36
44
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<!-- <span class="icon icon-budget"></span> -->
|
|
46
|
-
<!-- <span class="icon-label">${translator.translate('headerBudget')}</span>, -->
|
|
47
|
-
${translator.translate('headerOr')}
|
|
48
|
-
<span class="icon icon-occasion"></span>
|
|
49
|
-
<span class="icon-label">${translator.translate('headerOccasion')}</span>
|
|
50
|
-
${translator.translate('headerLine2')}
|
|
51
|
-
</h1>`
|
|
45
|
+
async initialize() {
|
|
46
|
+
await translator.fetchConfig('storeName');
|
|
47
|
+
this.storeId = translator.storeId;
|
|
48
|
+
this.ui.initializeWidget();
|
|
49
|
+
this.search.addEventListeners();
|
|
50
|
+
this.ui.initializeSearchAnimation();
|
|
51
|
+
this.search.loadInstantRecommendations();
|
|
52
|
+
this.language.createLanguageSwitcher();
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<div class="wine-widget-wrapper">
|
|
59
|
-
<div id="wine-widget-header">
|
|
60
|
-
${this.getTitle(this.titleText)}
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
<div id="wine-widget-search">
|
|
64
|
-
<!-- Иконка поиска слева -->
|
|
65
|
-
<span class="search-icon"></span>
|
|
66
|
-
|
|
67
|
-
<!-- Поле ввода -->
|
|
68
|
-
<input
|
|
69
|
-
type="text"
|
|
70
|
-
id="wine-search-input"
|
|
71
|
-
placeholder="${translator.translate('searchPlaceholder')}">
|
|
72
|
-
|
|
73
|
-
<!-- Кнопка -->
|
|
74
|
-
<button id="wine-search-button">
|
|
75
|
-
${translator.translate('searchButton')}
|
|
76
|
-
</button>
|
|
77
|
-
</div>
|
|
78
|
-
<div id="wine-widget-right">
|
|
79
|
-
<div id="loading-indicator" class="hidden">
|
|
80
|
-
<div id="typing-indicator" class="typing-indicator">
|
|
81
|
-
<span></span><span></span><span></span>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
<div id="wine-placeholder"></div>
|
|
85
|
-
</div>
|
|
86
|
-
<div id="powered-by-widget">
|
|
87
|
-
<div class="powered-by-center">
|
|
88
|
-
<a href="https://wineater.com" target="_blank">
|
|
89
|
-
<span>${translator.translate('poweredBy')}</span>
|
|
90
|
-
<img src="${this.countryCode === 'ru' ? 'https://czvgkhagwvmknscoerfy.supabase.co/storage/v1/object/public/static-media/divino_logo.svg?x=5' : 'https://czvgkhagwvmknscoerfy.supabase.co/storage/v1/object/public/static-media/logo_medium_gray.svg'}" alt="Wineater Logo">
|
|
91
|
-
<span class="powered-by-sommelier">${translator.translate('AI sommelier')}</span>
|
|
92
|
-
</a>
|
|
93
|
-
</div>
|
|
94
|
-
<div class="powered-by-lang"></div>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
`;
|
|
99
|
-
widgetContainer.insertAdjacentHTML('beforeend', widgetHTML);
|
|
100
|
-
this.addEventListenersStoreVersion();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
addEventListenersStoreVersion() {
|
|
104
|
-
document
|
|
105
|
-
.getElementById('wine-search-button')
|
|
106
|
-
.addEventListener('click', () => {
|
|
107
|
-
this.sendEvent('searchButtonClicked', { query: document.getElementById('wine-search-input').value });
|
|
108
|
-
this.handleSearch();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Привязка фильтров
|
|
112
|
-
this.setupFilterButtons('price-buttons', 'price');
|
|
113
|
-
this.setupFilterButtons('color-buttons', 'color');
|
|
114
|
-
this.setupFilterButtons('intensity-buttons', 'intensity');
|
|
115
|
-
|
|
116
|
-
// Обработчик Enter в поле ввода
|
|
117
|
-
document
|
|
118
|
-
.getElementById('wine-search-input')
|
|
119
|
-
.addEventListener('keydown', (event) => {
|
|
120
|
-
if (event.key === 'Enter') {
|
|
121
|
-
this.handleSearch();
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
}
|
|
55
|
+
displayResults(wines, isAnimated = false) {
|
|
56
|
+
const resultsContainer = document.getElementById('wine-placeholder');
|
|
57
|
+
this.ui.hideLoadingIndicator();
|
|
58
|
+
resultsContainer.innerHTML = '';
|
|
125
59
|
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
button.addEventListener('click', (e) => {
|
|
130
|
-
buttons.forEach(btn => btn.classList.remove('selected'));
|
|
131
|
-
e.target.classList.add('selected');
|
|
132
|
-
this.selectedFilters[filterKey] = e.target.dataset.value;
|
|
133
|
-
this.handleSearch();
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
}
|
|
60
|
+
const getValue = (wine, key) => wine[key] || wine[key.charAt(0).toUpperCase() + key.slice(1)];
|
|
61
|
+
const joinParams = (wine, keys) => keys.map(key => getValue(wine, key)).filter(Boolean).join(' • ');
|
|
62
|
+
const createTagHTML = tags => Array.isArray(tags) ? tags.map(tag => `<span class="popup-tag">${tag}</span>`).join('') : '';
|
|
137
63
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
if (filters.color) {
|
|
150
|
-
filterPhrases.push(`color: ${filters.color || 'any'}; `);
|
|
151
|
-
}
|
|
152
|
-
if (filters.intensity) {
|
|
153
|
-
filterPhrases.push(`intensity: ${filters.intensity || 'any'}; `);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Объединяем фильтры с основным запросом
|
|
157
|
-
const fullQuery = filterPhrases.length > 0
|
|
158
|
-
? `${query} ${filterPhrases.join(', ')}`
|
|
159
|
-
: query;
|
|
160
|
-
|
|
161
|
-
// Логирование запроса
|
|
162
|
-
console.log('Searching for:', fullQuery);
|
|
163
|
-
console.log('Applied filters:', filters);
|
|
64
|
+
const createPopupHTML = (wine) => {
|
|
65
|
+
const tags = wine.tags || wine.Tags;
|
|
66
|
+
const text = wine.reason || wine['Short Description'];
|
|
67
|
+
if (!text && !tags) return '';
|
|
68
|
+
|
|
69
|
+
return `
|
|
70
|
+
<div class="wine-hover-popup">
|
|
71
|
+
${text ? `<div class="popup-reason">${text}</div>` : ''}
|
|
72
|
+
${tags ? `<div class="popup-tags">${createTagHTML(tags)}</div>` : ''}
|
|
73
|
+
</div>`;
|
|
74
|
+
};
|
|
164
75
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
this.animateCardDisappearance();
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
// Вызываем API метод для получения рекомендаций
|
|
172
|
-
const results = await this.api.getWineRecommendationsSearch(fullQuery);
|
|
173
|
-
|
|
174
|
-
// Проверяем, есть ли данные
|
|
175
|
-
if (results && results.length > 0) {
|
|
176
|
-
console.log('Recommendations:', results);
|
|
177
|
-
// Отображаем результаты
|
|
178
|
-
this.displayResults(results, true);
|
|
179
|
-
} else {
|
|
180
|
-
console.log('No recommendations found.');
|
|
181
|
-
this.displayNoResultsMessage();
|
|
76
|
+
const formatPrice = (price) => {
|
|
77
|
+
if (typeof price !== 'number') {
|
|
78
|
+
price = Number(price);
|
|
182
79
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
this.displayErrorMessage();
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
enableHoverPopups() {
|
|
190
|
-
const tiles = document.querySelectorAll('.wine-recommendation-tile');
|
|
191
|
-
|
|
192
|
-
tiles.forEach(tile => {
|
|
193
|
-
const popup = tile.querySelector('.wine-hover-popup');
|
|
194
|
-
if (!popup) return;
|
|
195
|
-
|
|
196
|
-
tile.addEventListener('mouseenter', () => {
|
|
197
|
-
popup.style.display = 'block';
|
|
198
|
-
popup.classList.add('slide-in');
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
tile.addEventListener('mouseleave', () => {
|
|
202
|
-
popup.classList.remove('slide-in');
|
|
203
|
-
popup.style.display = 'none';
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
}
|
|
80
|
+
return price.toLocaleString('en-US');
|
|
81
|
+
};
|
|
207
82
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
${text ? `<div class="popup-reason">${text}</div>` : ''}
|
|
231
|
-
${tags ? `<div class="popup-tags">${createTagHTML(tags)}</div>` : ''}
|
|
232
|
-
</div>`;
|
|
233
|
-
};
|
|
83
|
+
wines.forEach(async (wine, index) => {
|
|
84
|
+
const title = getValue(wine, 'title');
|
|
85
|
+
const image = getValue(wine, 'image');
|
|
86
|
+
const price = getValue(wine, 'total');
|
|
87
|
+
const url = getValue(wine, 'url');
|
|
88
|
+
const tags = wine.tags || wine.Tags;
|
|
89
|
+
const line1 = joinParams(wine, ['type', 'country', 'region', 'grape']);
|
|
90
|
+
const tagsHTML = tags ? createTagHTML(tags) : '';
|
|
91
|
+
const reasonPopupHTML = createPopupHTML(wine);
|
|
92
|
+
const shortDescription = wine['Short Description'];
|
|
93
|
+
const pairing = wine.pairing_recommendation;
|
|
94
|
+
const isSomm = wine.isSommSelection;
|
|
95
|
+
|
|
96
|
+
const wineHTML = `
|
|
97
|
+
<div class="wine-recommendation-tile ${isSomm ? 'somm-selection' : ''}">
|
|
98
|
+
${isSomm ? `<div class="somm-selection-badge">${translator.translate('wineDetails.sommSelection')}</div>` : ''}
|
|
99
|
+
|
|
100
|
+
<div class="tile-top">
|
|
101
|
+
${image ? `<img src="${image}" alt="${title}" class="wine-image" />` : ''}
|
|
102
|
+
<div class="wine-price">${formatPrice(price)} ${translator.getCurrency('label')}</div>
|
|
103
|
+
<h3 class="wine-title">${title}</h3>
|
|
104
|
+
</div>
|
|
234
105
|
|
|
235
|
-
|
|
236
|
-
if (typeof price !== 'number') {
|
|
237
|
-
price = Number(price);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return price.toLocaleString('en-US'); // e.g., 1000 -> "1,000"
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
wines.forEach(async (wine, index) => {
|
|
244
|
-
const title = getValue(wine, 'title');
|
|
245
|
-
const image = getValue(wine, 'image');
|
|
246
|
-
const price = getValue(wine, 'total');
|
|
247
|
-
const url = getValue(wine, 'url');
|
|
248
|
-
const tags = wine.tags || wine.Tags;
|
|
249
|
-
const line1 = joinParams(wine, ['type', 'country', 'region', 'grape']);
|
|
250
|
-
const tagsHTML = tags ? createTagHTML(tags) : '';
|
|
251
|
-
const reasonPopupHTML = createPopupHTML(wine);
|
|
252
|
-
const shortDescription = wine['Short Description'];
|
|
253
|
-
const pairing = wine.pairing_recommendation;
|
|
254
|
-
const isSomm = wine.isSommSelection;
|
|
255
|
-
|
|
256
|
-
const wineHTML = `
|
|
257
|
-
<div class="wine-recommendation-tile ${isSomm ? 'somm-selection' : ''}">
|
|
258
|
-
${isSomm ? `<div class="somm-selection-badge">${translator.translate('wineDetails.sommSelection')}</div>` : ''}
|
|
259
|
-
|
|
260
|
-
<div class="tile-top">
|
|
261
|
-
${image ? `<img src="${image}" alt="${title}" class="wine-image" />` : ''}
|
|
262
|
-
<div class="wine-price">${formatPrice(price)} ${translator.getCurrency('label')}</div>
|
|
263
|
-
<h3 class="wine-title">${title}</h3>
|
|
264
|
-
</div>
|
|
265
|
-
|
|
266
|
-
${line1 ? `<div class="wine-params">${line1}</div>` : ''}
|
|
267
|
-
|
|
268
|
-
<div class="bottom-row">
|
|
269
|
-
${url
|
|
270
|
-
? `<a class="buy-btn" href="${url}" target="_blank">${translator.translate('wineDetails.buy')}</a>`
|
|
271
|
-
: `<button class="buy-btn" data-toggle-info>${translator.translate('moreInfo')}</button>`
|
|
272
|
-
}
|
|
273
|
-
</div>
|
|
274
|
-
|
|
275
|
-
<div class="card-popup hidden" data-wine-index="${index}">
|
|
276
|
-
<div class="card-popup-header">
|
|
277
|
-
<h2>${title}</h2>
|
|
278
|
-
<span class="card-popup-close">×</span>
|
|
279
|
-
</div>
|
|
280
|
-
${shortDescription ? `<p class="popup-short-desc" data-orig="${shortDescription}">${shortDescription}</p>` : ''}
|
|
281
|
-
${wine.reason ? `<div class="popup-reason" data-orig="${wine.reason}">${wine.reason}</div>`
|
|
282
|
-
: pairing ? `<p class="popup-pairing" data-orig="${pairing}">${pairing}</p>` : ''}
|
|
283
|
-
${tagsHTML ? `<div class="popup-tags">${tagsHTML}</div>` : ''}
|
|
284
|
-
</div>
|
|
285
|
-
|
|
286
|
-
${reasonPopupHTML}
|
|
287
|
-
</div>
|
|
288
|
-
`;
|
|
289
|
-
|
|
290
|
-
resultsContainer.insertAdjacentHTML('beforeend', wineHTML);
|
|
291
|
-
const currentCard = resultsContainer.children[index];
|
|
292
|
-
|
|
293
|
-
if (!url) {
|
|
294
|
-
const buyBtn = currentCard.querySelector('[data-toggle-info]');
|
|
295
|
-
const popup = currentCard.querySelector('.card-popup');
|
|
296
|
-
const closeBtn = currentCard.querySelector('.card-popup-close');
|
|
297
|
-
|
|
298
|
-
closeBtn?.addEventListener('click', (e) => {
|
|
299
|
-
e.stopPropagation();
|
|
300
|
-
popup.classList.add('hidden');
|
|
301
|
-
});
|
|
106
|
+
${line1 ? `<div class="wine-params">${line1}</div>` : ''}
|
|
302
107
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (!isInsideAnyPopup) {
|
|
310
|
-
document.querySelectorAll('.card-popup').forEach(p => p.classList.add('hidden'));
|
|
311
|
-
document.querySelectorAll('.mobile-fullscreen-wrapper').forEach(wrapper => wrapper.remove());
|
|
312
|
-
}
|
|
313
|
-
});
|
|
108
|
+
<div class="bottom-row">
|
|
109
|
+
${url
|
|
110
|
+
? `<a class="buy-btn" href="${url}" target="_blank">${translator.translate('wineDetails.buy')}</a>`
|
|
111
|
+
: `<button class="buy-btn" data-toggle-info>${translator.translate('moreInfo')}</button>`
|
|
112
|
+
}
|
|
113
|
+
</div>
|
|
314
114
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
// Close all open popups
|
|
330
|
-
document.querySelectorAll('.card-popup').forEach(p => p.classList.add('hidden'));
|
|
331
|
-
document.querySelectorAll('.mobile-fullscreen-wrapper').forEach(p => p.remove());
|
|
332
|
-
|
|
333
|
-
if (window.innerWidth <= 768) {
|
|
334
|
-
const popupContent = popup.innerHTML;
|
|
335
|
-
|
|
336
|
-
const overlay = document.createElement('div');
|
|
337
|
-
overlay.className = 'mobile-fullscreen-wrapper';
|
|
338
|
-
overlay.innerHTML = `
|
|
339
|
-
<div class="mobile-fullscreen-overlay"></div>
|
|
340
|
-
<div class="card-popup mobile-fullscreen-popup">${popupContent}</div>
|
|
341
|
-
`;
|
|
342
|
-
|
|
343
|
-
overlay.querySelector('.card-popup-close')?.addEventListener('click', () => overlay.remove());
|
|
344
|
-
|
|
345
|
-
document.body.appendChild(overlay);
|
|
346
|
-
} else {
|
|
347
|
-
popup.classList.remove('hidden');
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
setTimeout(() => {
|
|
354
|
-
currentCard.style.opacity = '1';
|
|
355
|
-
currentCard.style.transform = 'translateY(0)';
|
|
356
|
-
}, isAnimated ? index * 200 : 0);
|
|
357
|
-
});
|
|
115
|
+
<div class="card-popup hidden" data-wine-index="${index}">
|
|
116
|
+
<div class="card-popup-header">
|
|
117
|
+
<h2>${title}</h2>
|
|
118
|
+
<span class="card-popup-close">×</span>
|
|
119
|
+
</div>
|
|
120
|
+
${shortDescription ? `<p class="popup-short-desc" data-orig="${shortDescription}">${shortDescription}</p>` : ''}
|
|
121
|
+
${wine.reason
|
|
122
|
+
? `<div class="popup-reason" data-orig="${wine.reason}">${wine.reason}</div>`
|
|
123
|
+
: pairing
|
|
124
|
+
? `<p class="popup-pairing" data-orig="${pairing}">${pairing}</p>`
|
|
125
|
+
: ''
|
|
126
|
+
}
|
|
127
|
+
${tagsHTML ? `<div class="popup-tags">${tagsHTML}</div>` : ''}
|
|
128
|
+
</div>
|
|
358
129
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
130
|
+
${reasonPopupHTML}
|
|
131
|
+
</div>
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
resultsContainer.insertAdjacentHTML('beforeend', wineHTML);
|
|
135
|
+
const currentCard = resultsContainer.children[index];
|
|
136
|
+
|
|
137
|
+
if (!url) {
|
|
138
|
+
const buyBtn = currentCard.querySelector('[data-toggle-info]');
|
|
139
|
+
const popup = currentCard.querySelector('.card-popup');
|
|
140
|
+
const closeBtn = currentCard.querySelector('.card-popup-close');
|
|
141
|
+
|
|
142
|
+
closeBtn?.addEventListener('click', (e) => {
|
|
143
|
+
e.stopPropagation();
|
|
144
|
+
popup.classList.add('hidden');
|
|
145
|
+
this.trackEvent(WIDGET_EVENTS.WINE_DETAILS_CLOSED, {
|
|
146
|
+
wine_id: wine.ID,
|
|
147
|
+
wine_title: wine.title || wine.Title
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
document.addEventListener('click', (e) => {
|
|
152
|
+
const isInsideAnyPopup =
|
|
153
|
+
e.target.closest('.card-popup') ||
|
|
154
|
+
e.target.closest('.mobile-fullscreen-popup') ||
|
|
155
|
+
e.target.closest('[data-toggle-info]');
|
|
156
|
+
|
|
157
|
+
if (!isInsideAnyPopup) {
|
|
158
|
+
document.querySelectorAll('.card-popup').forEach(p => p.classList.add('hidden'));
|
|
159
|
+
document.querySelectorAll('.mobile-fullscreen-wrapper').forEach(wrapper => wrapper.remove());
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (this.widgetType === 'store') {
|
|
164
|
+
currentCard.addEventListener('mouseenter', () => {
|
|
165
|
+
popup.classList.remove('hidden');
|
|
166
|
+
this.trackEvent(WIDGET_EVENTS.WINE_CARD_HOVER, {
|
|
167
|
+
wine_id: wine.ID,
|
|
168
|
+
wine_title: wine.title || wine.Title
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
currentCard.addEventListener('mouseleave', () => {
|
|
173
|
+
popup.classList.add('hidden');
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
buyBtn?.addEventListener('click', (e) => {
|
|
177
|
+
e.stopPropagation();
|
|
178
|
+
|
|
179
|
+
document.querySelectorAll('.card-popup').forEach(p => p.classList.add('hidden'));
|
|
180
|
+
document.querySelectorAll('.mobile-fullscreen-wrapper').forEach(p => p.remove());
|
|
181
|
+
|
|
182
|
+
if (window.innerWidth <= 768) {
|
|
183
|
+
const popupContent = popup.innerHTML;
|
|
184
|
+
const overlay = document.createElement('div');
|
|
185
|
+
overlay.className = 'mobile-fullscreen-wrapper';
|
|
186
|
+
overlay.innerHTML = `
|
|
187
|
+
<div class="mobile-fullscreen-overlay"></div>
|
|
188
|
+
<div class="card-popup mobile-fullscreen-popup">${popupContent}</div>
|
|
189
|
+
`;
|
|
190
|
+
|
|
191
|
+
overlay.querySelector('.card-popup-close')?.addEventListener('click', () => overlay.remove());
|
|
192
|
+
document.body.appendChild(overlay);
|
|
193
|
+
} else {
|
|
194
|
+
popup.classList.remove('hidden');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.trackEvent(WIDGET_EVENTS.WINE_DETAILS_VIEWED, {
|
|
198
|
+
wine_id: wine.ID,
|
|
199
|
+
wine_title: wine.title || wine.Title,
|
|
200
|
+
is_mobile: window.innerWidth <= 768
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
const buyBtn = currentCard.querySelector('.buy-btn');
|
|
206
|
+
buyBtn?.addEventListener('click', () => {
|
|
207
|
+
this.trackEvent(WIDGET_EVENTS.BUY_BUTTON_CLICKED, {
|
|
208
|
+
wine_id: wine.ID,
|
|
209
|
+
wine_title: wine.title || wine.Title,
|
|
210
|
+
wine_url: wine.url || wine.URL
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
365
214
|
|
|
366
|
-
animateCardDisappearance() {
|
|
367
|
-
const cards = document.querySelectorAll('.wine-recommendation-tile');
|
|
368
|
-
cards.forEach((card, index) => {
|
|
369
215
|
setTimeout(() => {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}, index *
|
|
216
|
+
currentCard.style.opacity = '1';
|
|
217
|
+
currentCard.style.transform = 'translateY(0)';
|
|
218
|
+
}, isAnimated ? index * 200 : 0);
|
|
373
219
|
});
|
|
374
220
|
|
|
375
|
-
|
|
376
|
-
document.getElementById('wine-placeholder').innerHTML = ''; // Удалить все карточки после анимации
|
|
377
|
-
this.showLoadingIndicator();
|
|
378
|
-
}, cards.length * 100 + 300); // Дождаться завершения анимации
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
showLoadingIndicator() {
|
|
382
|
-
const loadingIndicator = document.getElementById('loading-indicator');
|
|
383
|
-
loadingIndicator.classList.remove('hidden');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
hideLoadingIndicator() {
|
|
387
|
-
const loadingIndicator = document.getElementById('loading-indicator');
|
|
388
|
-
loadingIndicator.classList.add('hidden');
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
displayNoResultsMessage() {
|
|
392
|
-
const resultsContainer = document.getElementById('wine-placeholder');
|
|
393
|
-
resultsContainer.innerHTML = `
|
|
394
|
-
<div class="no-results">
|
|
395
|
-
<p>${translator.translate('noResultsMessage') || 'No recommendations found for your query.'}</p>
|
|
396
|
-
</div>
|
|
397
|
-
`;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
displayErrorMessage() {
|
|
401
|
-
const resultsContainer = document.getElementById('wine-placeholder');
|
|
402
|
-
resultsContainer.innerHTML = `
|
|
403
|
-
<div class="error-message">
|
|
404
|
-
<p>${translator.translate('errorMessage') || 'Something went wrong. Please try again later.'}</p>
|
|
405
|
-
</div>
|
|
406
|
-
`;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
async translatePopupContent(wines) {
|
|
410
|
-
console.log(translator.language, translator.storeLanguage);
|
|
411
|
-
if (translator.language === translator.storeLanguage || this.widgetType !== 'bar') {
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const possibleFields = ['Short Description', 'reason', 'pairing_recommendation', 'Tags', 'tags'];
|
|
416
|
-
const firstWine = wines[0];
|
|
417
|
-
|
|
418
|
-
const fieldsToTranslate = possibleFields.filter(field => field in firstWine);
|
|
419
|
-
const batchSize = 4;
|
|
420
|
-
|
|
421
|
-
for (let i = 0; i < wines.length; i += batchSize) {
|
|
422
|
-
const batch = wines.slice(i, i + batchSize);
|
|
423
|
-
|
|
424
|
-
const translationPromises = batch.map(async (wine, index) => {
|
|
425
|
-
const globalIndex = i + index;
|
|
426
|
-
|
|
427
|
-
// Сбор оригинальных текстов в формате [{ key, text }]
|
|
428
|
-
const entries = fieldsToTranslate
|
|
429
|
-
.map(field => {
|
|
430
|
-
const val = wine[field] || wine[field.toLowerCase()];
|
|
431
|
-
if (!val) return null;
|
|
432
|
-
return { key: field, text: Array.isArray(val) ? val.join(', ') : val };
|
|
433
|
-
})
|
|
434
|
-
.filter(Boolean);
|
|
435
|
-
|
|
436
|
-
if (entries.length === 0) return;
|
|
437
|
-
|
|
438
|
-
// Перевод
|
|
439
|
-
const translatedArray = await translator.translateTexts(
|
|
440
|
-
Object.fromEntries(entries.map(entry => [entry.key, entry.text]))
|
|
441
|
-
);
|
|
442
|
-
|
|
443
|
-
const popup = document.querySelector(`.card-popup[data-wine-index="${globalIndex}"]`);
|
|
444
|
-
if (!popup) return;
|
|
445
|
-
|
|
446
|
-
// Применение перевода
|
|
447
|
-
if (translatedArray['Short Description']) {
|
|
448
|
-
const el = popup.querySelector('.popup-short-desc');
|
|
449
|
-
if (el) el.textContent = translatedArray['Short Description'];
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (translatedArray.reason) {
|
|
453
|
-
const el = popup.querySelector('.popup-reason');
|
|
454
|
-
if (el) el.textContent = translatedArray.reason;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
if (translatedArray.pairing_recommendation) {
|
|
458
|
-
const el = popup.querySelector('.popup-pairing');
|
|
459
|
-
if (el) el.textContent = translatedArray.pairing_recommendation;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const tags = translatedArray.tags || translatedArray.Tags;
|
|
463
|
-
if (tags) {
|
|
464
|
-
const tagArray = tags.split(',').map(tag => tag.trim());
|
|
465
|
-
const tagHTML = tagArray.map(tag => `<span class="popup-tag">${tag}</span>`).join('');
|
|
466
|
-
const tagsContainer = popup.querySelector('.popup-tags');
|
|
467
|
-
if (tagsContainer) tagsContainer.innerHTML = tagHTML;
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
await Promise.all(translationPromises);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
221
|
+
this.language.translatePopupContent(wines);
|
|
474
222
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
try {
|
|
478
|
-
const recommendations = await this.api.getInstantRecommendations();
|
|
479
|
-
this.displayResults(recommendations); // Обновляем отображение рекомендаций
|
|
480
|
-
} catch (error) {
|
|
481
|
-
console.error('Error loading instant recommendations:', error.message);
|
|
223
|
+
if (this.widgetType === 'store') {
|
|
224
|
+
this.ui.enableHoverPopups();
|
|
482
225
|
}
|
|
483
226
|
}
|
|
484
|
-
|
|
485
|
-
initializeSearchAnimation() {
|
|
486
|
-
const searchInput = document.getElementById('wine-search-input');
|
|
487
|
-
const suggestions = translator.translate('placeholderSuggestions');
|
|
488
|
-
let index = 0;
|
|
489
|
-
let charIndex = 0;
|
|
490
|
-
let typingForward = true;
|
|
491
|
-
let isInputFocused = false;
|
|
492
|
-
|
|
493
|
-
const typeEffect = () => {
|
|
494
|
-
if (isInputFocused) return; // Если фокус в поле ввода, эффект останавливается
|
|
495
|
-
if (typingForward) {
|
|
496
|
-
searchInput.placeholder = suggestions[index].substring(0, charIndex++);
|
|
497
|
-
if (charIndex > suggestions[index].length) {
|
|
498
|
-
typingForward = false;
|
|
499
|
-
setTimeout(typeEffect, 2000); // Пауза после завершения строки
|
|
500
|
-
} else {
|
|
501
|
-
setTimeout(typeEffect, 100); // Скорость ввода
|
|
502
|
-
}
|
|
503
|
-
} else {
|
|
504
|
-
searchInput.placeholder = suggestions[index].substring(0, charIndex--);
|
|
505
|
-
if (charIndex < 0) {
|
|
506
|
-
typingForward = true;
|
|
507
|
-
index = (index + 1) % suggestions.length; // Переход к следующей подсказке
|
|
508
|
-
setTimeout(typeEffect, 500); // Пауза перед началом следующей строки
|
|
509
|
-
} else {
|
|
510
|
-
setTimeout(typeEffect, 50); // Скорость удаления
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
|
|
515
|
-
typeEffect();
|
|
516
|
-
|
|
517
|
-
// Очистка строки и остановка эффекта при фокусе
|
|
518
|
-
searchInput.addEventListener('focus', () => {
|
|
519
|
-
searchInput.placeholder = '';
|
|
520
|
-
isInputFocused = true; // Останавливаем эффект
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
// Возобновление эффекта при потере фокуса
|
|
524
|
-
searchInput.addEventListener('blur', () => {
|
|
525
|
-
isInputFocused = false; // Возобновляем эффект
|
|
526
|
-
charIndex = 0;
|
|
527
|
-
typingForward = true;
|
|
528
|
-
typeEffect();
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
createLanguageSwitcher() {
|
|
533
|
-
const langContainer = document.querySelector('#powered-by-widget .powered-by-lang');
|
|
534
|
-
const languages = translator.getSupportedLanguages();
|
|
535
|
-
if (!languages) return;
|
|
536
|
-
|
|
537
|
-
const currentLanguage = translator.language;
|
|
538
|
-
|
|
539
|
-
const dropdown = document.createElement('select');
|
|
540
|
-
dropdown.id = 'language-dropdown';
|
|
541
|
-
dropdown.className = 'language-dropdown';
|
|
542
|
-
|
|
543
|
-
languages.forEach(lang => {
|
|
544
|
-
const option = document.createElement('option');
|
|
545
|
-
option.value = lang;
|
|
546
|
-
option.textContent = lang.toUpperCase();
|
|
547
|
-
option.selected = lang === currentLanguage;
|
|
548
|
-
dropdown.appendChild(option);
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
dropdown.onchange = (event) => {
|
|
552
|
-
const selectedLang = event.target.value;
|
|
553
|
-
|
|
554
|
-
// Обновляем URL без перезагрузки страницы
|
|
555
|
-
const url = new URL(window.location.href);
|
|
556
|
-
url.searchParams.set('lang', selectedLang);
|
|
557
|
-
|
|
558
|
-
// Обновляем localStorage тоже, на всякий случай
|
|
559
|
-
localStorage.setItem('selectedLanguage', selectedLang);
|
|
560
|
-
|
|
561
|
-
// Перенаправляем пользователя с обновлённым URL
|
|
562
|
-
window.location.href = url.toString();
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
langContainer.appendChild(dropdown);
|
|
566
|
-
}
|
|
567
227
|
}
|