@zenky/storefront-vue 0.9.32
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/LICENSE.md +21 -0
- package/README.md +15 -0
- package/dist/cjs/index.js +1913 -0
- package/dist/esm/index.js +1849 -0
- package/dist/zenky.d.ts +1167 -0
- package/package.json +43 -0
|
@@ -0,0 +1,1913 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var vue = require('vue');
|
|
6
|
+
var lodashEs = require('lodash-es');
|
|
7
|
+
var api = require('@zenky/api');
|
|
8
|
+
var pinia = require('pinia');
|
|
9
|
+
var ui = require('@zenky/ui');
|
|
10
|
+
var events = require('@zenky/events');
|
|
11
|
+
|
|
12
|
+
function useDataDestroyer(destroyer, errorHandler) {
|
|
13
|
+
const destroying = vue.ref(false);
|
|
14
|
+
const destroy = async (id) => {
|
|
15
|
+
if (destroying.value) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
destroying.value = true;
|
|
19
|
+
try {
|
|
20
|
+
await destroyer(id);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
errorHandler(e);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
destroying.value = false;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
destroying,
|
|
33
|
+
destroy,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getApiErrorHandler(providedHandler, defaultHandler) {
|
|
38
|
+
return providedHandler ? providedHandler : defaultHandler;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
exports.ZenkyInjection = void 0;
|
|
42
|
+
(function (ZenkyInjection) {
|
|
43
|
+
ZenkyInjection["ProductsSource"] = "zenky:products:source";
|
|
44
|
+
ZenkyInjection["Category"] = "zenky:category";
|
|
45
|
+
ZenkyInjection["CategoryNotFound"] = "zenky:category:notFound";
|
|
46
|
+
ZenkyInjection["Collection"] = "zenky:collection";
|
|
47
|
+
ZenkyInjection["CollectionNotFound"] = "zenky:collection:notFound";
|
|
48
|
+
})(exports.ZenkyInjection || (exports.ZenkyInjection = {}));
|
|
49
|
+
|
|
50
|
+
async function useStateDataLoader(state, loader) {
|
|
51
|
+
if (state.value.loaded) {
|
|
52
|
+
// Data was already loaded, return it.
|
|
53
|
+
return state.value.data;
|
|
54
|
+
}
|
|
55
|
+
else if (state.value.loading) {
|
|
56
|
+
// If data is in loading state, return promise that will be resolved after data loaded.
|
|
57
|
+
return new Promise(function (resolve) {
|
|
58
|
+
const loaded = vue.computed(() => state.value.loaded);
|
|
59
|
+
const stop = vue.watch(loaded, function (val) {
|
|
60
|
+
if (val) {
|
|
61
|
+
resolve(state.value.data);
|
|
62
|
+
stop();
|
|
63
|
+
}
|
|
64
|
+
}, { immediate: true });
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Data is not loading nor was loaded, dispatch loader now.
|
|
68
|
+
await loader();
|
|
69
|
+
return state.value.data;
|
|
70
|
+
}
|
|
71
|
+
function usePaginatedLoader(provider, errorHandler) {
|
|
72
|
+
const state = vue.ref({
|
|
73
|
+
waiting: false,
|
|
74
|
+
loading: false,
|
|
75
|
+
loaded: false,
|
|
76
|
+
});
|
|
77
|
+
const pagination = vue.ref(null);
|
|
78
|
+
const items = vue.ref([]);
|
|
79
|
+
const load = async (request, reset = false) => {
|
|
80
|
+
if (state.value.loading) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (reset) {
|
|
84
|
+
state.value.loaded = false;
|
|
85
|
+
}
|
|
86
|
+
state.value.loading = true;
|
|
87
|
+
if (!state.value.loaded) {
|
|
88
|
+
state.value.waiting = true;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const { pagination: currentPagination, items: currentItems, } = await provider(request);
|
|
92
|
+
pagination.value = currentPagination;
|
|
93
|
+
if (reset) {
|
|
94
|
+
items.value = currentItems;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
currentItems.forEach((item) => items.value.push(item));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
errorHandler(e);
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
state.value.loading = false;
|
|
105
|
+
state.value.waiting = false;
|
|
106
|
+
state.value.loaded = true;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
state,
|
|
111
|
+
pagination,
|
|
112
|
+
items,
|
|
113
|
+
load,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function useListLoader(provider, errorHandler) {
|
|
117
|
+
const state = vue.ref({
|
|
118
|
+
loading: false,
|
|
119
|
+
loaded: false,
|
|
120
|
+
});
|
|
121
|
+
const items = vue.ref([]);
|
|
122
|
+
const load = async (request) => {
|
|
123
|
+
if (state.value.loading || state.value.loaded) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
state.value.loading = true;
|
|
127
|
+
try {
|
|
128
|
+
items.value = await provider(request);
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
errorHandler(e);
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
state.value.loading = false;
|
|
135
|
+
state.value.loaded = true;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
state,
|
|
140
|
+
items,
|
|
141
|
+
load,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function useItemLoader(provider, errorHandler) {
|
|
145
|
+
const state = vue.ref({
|
|
146
|
+
loading: false,
|
|
147
|
+
loaded: false,
|
|
148
|
+
});
|
|
149
|
+
const item = vue.ref(null);
|
|
150
|
+
const load = async (id, request) => {
|
|
151
|
+
if (state.value.loading || state.value.loaded) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
state.value.loading = true;
|
|
155
|
+
try {
|
|
156
|
+
item.value = await provider(id, request);
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
errorHandler(e);
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
state.value.loading = false;
|
|
163
|
+
state.value.loaded = true;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
state,
|
|
168
|
+
item,
|
|
169
|
+
load,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function useDataLoader(provider, errorHandler) {
|
|
173
|
+
const state = vue.ref({
|
|
174
|
+
loading: false,
|
|
175
|
+
loaded: false,
|
|
176
|
+
});
|
|
177
|
+
const data = vue.ref(null);
|
|
178
|
+
const load = async () => {
|
|
179
|
+
if (state.value.loading || state.value.loaded) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
state.value.loading = true;
|
|
183
|
+
try {
|
|
184
|
+
data.value = await provider();
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
errorHandler(e);
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
state.value.loading = false;
|
|
191
|
+
state.value.loaded = true;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
state,
|
|
196
|
+
data,
|
|
197
|
+
load,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let prefix = null;
|
|
202
|
+
const getPrefixedKey = (key) => (prefix ? `${prefix}:${key}` : key);
|
|
203
|
+
const storage = {
|
|
204
|
+
get(key, defaultValue) {
|
|
205
|
+
const item = localStorage.getItem(getPrefixedKey(key));
|
|
206
|
+
if (item === undefined || item === null) {
|
|
207
|
+
return defaultValue;
|
|
208
|
+
}
|
|
209
|
+
return JSON.parse(item).value;
|
|
210
|
+
},
|
|
211
|
+
set(key, value) {
|
|
212
|
+
localStorage.setItem(getPrefixedKey(key), JSON.stringify({ value }));
|
|
213
|
+
},
|
|
214
|
+
delete(key) {
|
|
215
|
+
this.set(key, null);
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
const createStorage = (storeId) => {
|
|
219
|
+
prefix = `zenky:storefront:${storeId}`;
|
|
220
|
+
return storage;
|
|
221
|
+
};
|
|
222
|
+
const getStorage = () => storage;
|
|
223
|
+
|
|
224
|
+
function getDaDataEntityName(data, types) {
|
|
225
|
+
return types.find((type) => data[type] !== 'undefined' && data[type]);
|
|
226
|
+
}
|
|
227
|
+
function getHousePart(house) {
|
|
228
|
+
if (Number.isNaN(parseInt(house, 10))) {
|
|
229
|
+
return `${house}`;
|
|
230
|
+
}
|
|
231
|
+
return `д ${house}`;
|
|
232
|
+
}
|
|
233
|
+
function getSettlementPart(suggestion) {
|
|
234
|
+
const city = suggestion.city_with_type;
|
|
235
|
+
const area = suggestion.area_with_type;
|
|
236
|
+
const region = suggestion.region_with_type;
|
|
237
|
+
if (city) {
|
|
238
|
+
return city === suggestion.settlement_with_type
|
|
239
|
+
? suggestion.settlement_with_type
|
|
240
|
+
: `${city}, ${suggestion.settlement_with_type}`;
|
|
241
|
+
}
|
|
242
|
+
else if (region && area) {
|
|
243
|
+
return `${region}, ${area}, ${suggestion.settlement_with_type}`;
|
|
244
|
+
}
|
|
245
|
+
else if (region) {
|
|
246
|
+
return `${region}, ${suggestion.settlement_with_type}`;
|
|
247
|
+
}
|
|
248
|
+
else if (area) {
|
|
249
|
+
return `${area}, ${suggestion.settlement_with_type}`;
|
|
250
|
+
}
|
|
251
|
+
return suggestion.settlement_with_type;
|
|
252
|
+
}
|
|
253
|
+
function getCityPart(suggestion) {
|
|
254
|
+
if (suggestion.settlement_with_type) {
|
|
255
|
+
return getSettlementPart(suggestion);
|
|
256
|
+
}
|
|
257
|
+
else if (suggestion.city_with_type) {
|
|
258
|
+
return suggestion.city_with_type;
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function getStockSchedule(schedule) {
|
|
264
|
+
if (!schedule) {
|
|
265
|
+
return 'Закрыто';
|
|
266
|
+
}
|
|
267
|
+
else if (!schedule.today.open && schedule.today.next_open_at) {
|
|
268
|
+
return `Откроется ${schedule.today.next_open_at.short}`;
|
|
269
|
+
}
|
|
270
|
+
else if (!schedule.today.open) {
|
|
271
|
+
return 'Закрыто';
|
|
272
|
+
}
|
|
273
|
+
else if (!schedule.today.closing_at) {
|
|
274
|
+
return `Сегодня: ${schedule.today.hours[0]}`;
|
|
275
|
+
}
|
|
276
|
+
const diff = schedule.today.closing_at.timestamp - (new Date().getTime() / 1000);
|
|
277
|
+
const min = 60 * 60;
|
|
278
|
+
if (diff < min) {
|
|
279
|
+
return `Закроется ${schedule.today.closing_at.diff.toLowerCase()}`;
|
|
280
|
+
}
|
|
281
|
+
return `Сегодня: ${schedule.today.hours[0]}`;
|
|
282
|
+
}
|
|
283
|
+
function getStockAddress(stock) {
|
|
284
|
+
if (!stock || !stock.address) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
return stock.address.address;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const useStoreStore = pinia.defineStore({
|
|
291
|
+
id: 'zenky:storefront:store',
|
|
292
|
+
state: () => {
|
|
293
|
+
const storage = getStorage();
|
|
294
|
+
return {
|
|
295
|
+
loading: false,
|
|
296
|
+
loaded: false,
|
|
297
|
+
store: null,
|
|
298
|
+
storeId: window.zenky.storeId,
|
|
299
|
+
cityId: storage.get('cityId'),
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
getters: {
|
|
303
|
+
cities() {
|
|
304
|
+
if (!this.loaded || !this.store || !this.store.cities) {
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
return this.store.cities;
|
|
308
|
+
},
|
|
309
|
+
city() {
|
|
310
|
+
if (!this.cityId) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
return this.cities.find((city) => city.id === this.cityId) || null;
|
|
314
|
+
},
|
|
315
|
+
stocks() {
|
|
316
|
+
if (!this.city || !this.city.stocks) {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
return this.city.stocks.filter((stock) => stock.visible);
|
|
320
|
+
},
|
|
321
|
+
settings() {
|
|
322
|
+
if (!this.store || !this.store.settings) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
return this.store.settings;
|
|
326
|
+
},
|
|
327
|
+
addressesResolver() {
|
|
328
|
+
if (!this.settings) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
return this.settings.addresses.resolver.id;
|
|
332
|
+
},
|
|
333
|
+
loyalty() {
|
|
334
|
+
if (!this.settings) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
return this.settings.loyalty;
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
actions: {
|
|
341
|
+
async loadStore(inclusions) {
|
|
342
|
+
if (this.loading || this.loaded) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
this.store = await api.getStore(inclusions);
|
|
347
|
+
this.loaded = true;
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
//
|
|
351
|
+
}
|
|
352
|
+
finally {
|
|
353
|
+
this.loading = false;
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
selectDefaultCity() {
|
|
357
|
+
if (this.city) {
|
|
358
|
+
return this.city;
|
|
359
|
+
}
|
|
360
|
+
else if (!this.cities.length) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
return this.selectCity(this.cities[0].id);
|
|
364
|
+
},
|
|
365
|
+
selectCity(cityId) {
|
|
366
|
+
const city = this.cities.find((item) => item.id === cityId);
|
|
367
|
+
if (!city) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const storage = getStorage();
|
|
371
|
+
storage.set('cityId', city.id);
|
|
372
|
+
this.cityId = city.id;
|
|
373
|
+
return city;
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
function useDadata() {
|
|
379
|
+
const { cityId } = useStoreStore();
|
|
380
|
+
const query = vue.ref(null);
|
|
381
|
+
const loading = vue.ref(false);
|
|
382
|
+
const results = vue.ref([]);
|
|
383
|
+
vue.watch(query, lodashEs.debounce(async () => {
|
|
384
|
+
if (loading.value || !query.value || !cityId) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
loading.value = true;
|
|
389
|
+
const { suggestions } = await api.getAddressSuggestions({
|
|
390
|
+
query: query.value,
|
|
391
|
+
city_id: cityId,
|
|
392
|
+
});
|
|
393
|
+
results.value = suggestions;
|
|
394
|
+
}
|
|
395
|
+
catch (e) {
|
|
396
|
+
//
|
|
397
|
+
}
|
|
398
|
+
finally {
|
|
399
|
+
loading.value = false;
|
|
400
|
+
}
|
|
401
|
+
}, 500));
|
|
402
|
+
return {
|
|
403
|
+
query,
|
|
404
|
+
loading,
|
|
405
|
+
results,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function useSuggestionsRestrictedToCity(city) {
|
|
409
|
+
const restricted = vue.computed(() => {
|
|
410
|
+
if (!city) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
else if (!city.settings) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
return city.settings.addresses.restrict_suggestions
|
|
417
|
+
&& city.settings.addresses.search_radius === null;
|
|
418
|
+
});
|
|
419
|
+
return {
|
|
420
|
+
restricted,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function getSelectedAddress(suggestion, house, block) {
|
|
424
|
+
const city = getCityPart(suggestion);
|
|
425
|
+
const street = suggestion[`${getDaDataEntityName(suggestion, ['street', 'settlement', 'city_district'])}_with_type`];
|
|
426
|
+
if (!city || !street) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
let address;
|
|
430
|
+
if (city !== street) {
|
|
431
|
+
address = `${city}, ${street}, ${getHousePart(house)}`;
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
address = `${city}, ${getHousePart(house)}`;
|
|
435
|
+
}
|
|
436
|
+
if (block) {
|
|
437
|
+
address += ` ${block}`;
|
|
438
|
+
}
|
|
439
|
+
return address;
|
|
440
|
+
}
|
|
441
|
+
function getSelectedAddressDisplayValue(suggestion) {
|
|
442
|
+
const entityName = getDaDataEntityName(suggestion, ['street', 'settlement', 'city_district']);
|
|
443
|
+
if (entityName === null) {
|
|
444
|
+
throw new Error('Selected address does not have a single field that we qualify as "street"');
|
|
445
|
+
}
|
|
446
|
+
const entityWithType = suggestion[`${entityName}_with_type`];
|
|
447
|
+
const cityEntityName = getDaDataEntityName(suggestion, ['city', 'settlement']);
|
|
448
|
+
const cityEntityWithType = suggestion[`${cityEntityName}_with_type`];
|
|
449
|
+
return {
|
|
450
|
+
short: entityWithType,
|
|
451
|
+
full: cityEntityWithType === entityWithType ? cityEntityWithType : `${cityEntityWithType}, ${entityWithType}`,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function getExistedAddressDisplayValue(address, restrictedToCity) {
|
|
455
|
+
const query = [];
|
|
456
|
+
if (address.city && !restrictedToCity) {
|
|
457
|
+
query.push(address.city.full);
|
|
458
|
+
}
|
|
459
|
+
if (address.settlement && query.indexOf(address.settlement) === -1) {
|
|
460
|
+
query.push(address.settlement);
|
|
461
|
+
}
|
|
462
|
+
if (address.street && query.indexOf(address.street.full) === -1) {
|
|
463
|
+
query.push(address.street.full);
|
|
464
|
+
}
|
|
465
|
+
return query.join(', ');
|
|
466
|
+
}
|
|
467
|
+
function getExistedAddress(address, house, block) {
|
|
468
|
+
const pieces = getExistedAddressDisplayValue(address, false).split(',');
|
|
469
|
+
if (house) {
|
|
470
|
+
pieces.push(`д ${house}`);
|
|
471
|
+
}
|
|
472
|
+
if (block) {
|
|
473
|
+
pieces.push(Number.isNaN(parseInt(block, 10)) ? block : `к ${block}`);
|
|
474
|
+
}
|
|
475
|
+
return pieces.join(', ');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const getAddressForm = () => ({
|
|
479
|
+
suggestion: null,
|
|
480
|
+
id: '',
|
|
481
|
+
city: '',
|
|
482
|
+
street: '',
|
|
483
|
+
house: '',
|
|
484
|
+
block: '',
|
|
485
|
+
address: '',
|
|
486
|
+
apartment: '',
|
|
487
|
+
entrance: '',
|
|
488
|
+
floor: '',
|
|
489
|
+
has_intercom: true,
|
|
490
|
+
});
|
|
491
|
+
function useAddressFormValue(form, resolver, address) {
|
|
492
|
+
const errors = [];
|
|
493
|
+
let data = {};
|
|
494
|
+
if (resolver === 'dadata') {
|
|
495
|
+
if (form.suggestion) {
|
|
496
|
+
data = {
|
|
497
|
+
address: getSelectedAddress(form.suggestion, form.house, form.block),
|
|
498
|
+
apartment: form.apartment,
|
|
499
|
+
entrance: form.entrance,
|
|
500
|
+
floor: form.floor,
|
|
501
|
+
has_intercom: lodashEs.isBoolean(form.has_intercom)
|
|
502
|
+
? form.has_intercom
|
|
503
|
+
: form.has_intercom === 'true',
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
else if (address) {
|
|
507
|
+
data = {
|
|
508
|
+
address: getExistedAddress(address, form.house, form.block),
|
|
509
|
+
apartment: form.apartment,
|
|
510
|
+
entrance: form.entrance,
|
|
511
|
+
floor: form.floor,
|
|
512
|
+
has_intercom: lodashEs.isBoolean(form.has_intercom)
|
|
513
|
+
? form.has_intercom
|
|
514
|
+
: form.has_intercom === 'true',
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
errors.push('Нужно выбрать улицу из выпадающего списка.');
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else if (resolver === 'plain') {
|
|
522
|
+
if (!form.city) {
|
|
523
|
+
errors.push('Нужно указать город.');
|
|
524
|
+
}
|
|
525
|
+
else if (!form.street) {
|
|
526
|
+
errors.push('Нужно указать улицу.');
|
|
527
|
+
}
|
|
528
|
+
else if (!form.house) {
|
|
529
|
+
errors.push('Нужно указать номер дома.');
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
data = {
|
|
533
|
+
city: form.city,
|
|
534
|
+
street: form.street,
|
|
535
|
+
house: form.house,
|
|
536
|
+
block: form.block,
|
|
537
|
+
apartment: form.apartment,
|
|
538
|
+
entrance: form.entrance,
|
|
539
|
+
floor: form.floor,
|
|
540
|
+
has_intercom: lodashEs.isBoolean(form.has_intercom)
|
|
541
|
+
? form.has_intercom
|
|
542
|
+
: form.has_intercom === 'true',
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
data,
|
|
548
|
+
errors,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const useCategoriesStore = pinia.defineStore({
|
|
553
|
+
id: 'zenky:storefront:categories',
|
|
554
|
+
state: () => ({
|
|
555
|
+
loading: false,
|
|
556
|
+
loaded: false,
|
|
557
|
+
tree: [],
|
|
558
|
+
flatTree: null,
|
|
559
|
+
}),
|
|
560
|
+
actions: {
|
|
561
|
+
async load() {
|
|
562
|
+
if (this.loading || this.loaded) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
this.loading = true;
|
|
566
|
+
try {
|
|
567
|
+
this.tree = await api.getCategoriesTree({ visibility: 'visible', with: 'media' });
|
|
568
|
+
this.loaded = true;
|
|
569
|
+
}
|
|
570
|
+
catch (e) {
|
|
571
|
+
//
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
this.loading = false;
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
setFlatTree(flatTree) {
|
|
578
|
+
this.flatTree = flatTree;
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
async function getTree() {
|
|
584
|
+
const store = useCategoriesStore();
|
|
585
|
+
const state = vue.computed(() => ({
|
|
586
|
+
loaded: store.loaded,
|
|
587
|
+
loading: store.loading,
|
|
588
|
+
data: store.tree,
|
|
589
|
+
}));
|
|
590
|
+
return useStateDataLoader(state, async () => store.load());
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const findCategory = (items, id) => {
|
|
594
|
+
for (let i = 0; i < items.length; ++i) {
|
|
595
|
+
if (items[i].id === id || items[i].short_id === id) {
|
|
596
|
+
return items[i];
|
|
597
|
+
}
|
|
598
|
+
const children = findCategory(items[i].children, id);
|
|
599
|
+
if (children !== null) {
|
|
600
|
+
return children;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return null;
|
|
604
|
+
};
|
|
605
|
+
function useCategories() {
|
|
606
|
+
const categories = vue.ref([]);
|
|
607
|
+
const load = async (ids) => {
|
|
608
|
+
const tree = await getTree();
|
|
609
|
+
ids.forEach((id) => {
|
|
610
|
+
const category = findCategory(tree, id);
|
|
611
|
+
if (category !== null) {
|
|
612
|
+
categories.value.push(category);
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
};
|
|
616
|
+
return {
|
|
617
|
+
categories,
|
|
618
|
+
load,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function useCategory() {
|
|
622
|
+
const category = vue.ref(null);
|
|
623
|
+
const load = async (id) => {
|
|
624
|
+
const tree = await getTree();
|
|
625
|
+
category.value = findCategory(tree, id);
|
|
626
|
+
};
|
|
627
|
+
return {
|
|
628
|
+
category,
|
|
629
|
+
load,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const generateFlatTree = (flat, categories, append = '') => {
|
|
634
|
+
categories.forEach((category) => {
|
|
635
|
+
flat.push({
|
|
636
|
+
...category,
|
|
637
|
+
children: [],
|
|
638
|
+
name: `${append}${category.name}`,
|
|
639
|
+
original_name: category.name,
|
|
640
|
+
});
|
|
641
|
+
if (category.children.length > 0) {
|
|
642
|
+
generateFlatTree(flat, category.children, `${append}${category.name} / `);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
};
|
|
646
|
+
function useFlatCategoriesTree() {
|
|
647
|
+
const tree = vue.ref([]);
|
|
648
|
+
const load = async () => {
|
|
649
|
+
const store = useCategoriesStore();
|
|
650
|
+
if (store.flatTree !== null) {
|
|
651
|
+
tree.value = store.flatTree;
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const flat = [];
|
|
655
|
+
generateFlatTree(flat, await getTree());
|
|
656
|
+
store.setFlatTree(flat);
|
|
657
|
+
tree.value = flat;
|
|
658
|
+
};
|
|
659
|
+
return {
|
|
660
|
+
tree,
|
|
661
|
+
load,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const findReversedCategories = (id, stack, categories) => {
|
|
666
|
+
for (let i = 0; i < categories.length; ++i) {
|
|
667
|
+
if (categories[i].id === id || categories[i].short_id === id) {
|
|
668
|
+
return {
|
|
669
|
+
category: categories[i],
|
|
670
|
+
stack,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
stack.push(categories[i]);
|
|
674
|
+
const children = findReversedCategories(id, stack, categories[i].children);
|
|
675
|
+
if (children !== null) {
|
|
676
|
+
return children;
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
stack.pop();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return null;
|
|
683
|
+
};
|
|
684
|
+
function useReversedTree() {
|
|
685
|
+
const tree = vue.ref([]);
|
|
686
|
+
const load = async (id) => {
|
|
687
|
+
const stack = [];
|
|
688
|
+
const result = findReversedCategories(id, stack, await getTree());
|
|
689
|
+
if (result === null) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
tree.value = [...result.stack, result.category];
|
|
693
|
+
};
|
|
694
|
+
return {
|
|
695
|
+
tree,
|
|
696
|
+
load,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function useSplittedCategoriesTree() {
|
|
701
|
+
const visible = vue.ref([]);
|
|
702
|
+
const hidden = vue.ref([]);
|
|
703
|
+
const load = async (split) => {
|
|
704
|
+
const tree = await getTree();
|
|
705
|
+
const unrefSplit = vue.unref(split);
|
|
706
|
+
visible.value = tree.slice(0, unrefSplit);
|
|
707
|
+
hidden.value = tree.slice(unrefSplit);
|
|
708
|
+
};
|
|
709
|
+
return {
|
|
710
|
+
visible,
|
|
711
|
+
hidden,
|
|
712
|
+
load,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function useCategoriesTree() {
|
|
717
|
+
const tree = vue.ref([]);
|
|
718
|
+
const load = async () => {
|
|
719
|
+
tree.value = await getTree();
|
|
720
|
+
};
|
|
721
|
+
return {
|
|
722
|
+
tree,
|
|
723
|
+
load,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const useCollectionsStore = pinia.defineStore({
|
|
728
|
+
id: 'zenky:storefront:collections',
|
|
729
|
+
state: () => ({
|
|
730
|
+
loading: false,
|
|
731
|
+
loaded: false,
|
|
732
|
+
collections: [],
|
|
733
|
+
}),
|
|
734
|
+
actions: {
|
|
735
|
+
async load() {
|
|
736
|
+
if (this.loading || this.loaded) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
this.loading = true;
|
|
740
|
+
try {
|
|
741
|
+
this.collections = (await api.getProductsCollections({
|
|
742
|
+
visibility: 'visible',
|
|
743
|
+
order_by: 'name',
|
|
744
|
+
with: 'media',
|
|
745
|
+
count: 100,
|
|
746
|
+
})).items;
|
|
747
|
+
this.loaded = true;
|
|
748
|
+
}
|
|
749
|
+
catch (e) {
|
|
750
|
+
//
|
|
751
|
+
}
|
|
752
|
+
finally {
|
|
753
|
+
this.loading = false;
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
async function getCollections() {
|
|
760
|
+
const store = useCollectionsStore();
|
|
761
|
+
const state = vue.computed(() => ({
|
|
762
|
+
loaded: store.loaded,
|
|
763
|
+
loading: store.loading,
|
|
764
|
+
data: store.collections,
|
|
765
|
+
}));
|
|
766
|
+
return useStateDataLoader(state, async () => store.load());
|
|
767
|
+
}
|
|
768
|
+
function useCollections() {
|
|
769
|
+
const collections = vue.ref([]);
|
|
770
|
+
const load = async (ids = null) => {
|
|
771
|
+
collections.value = [];
|
|
772
|
+
const items = await getCollections();
|
|
773
|
+
if (ids === null) {
|
|
774
|
+
collections.value = items;
|
|
775
|
+
}
|
|
776
|
+
else if (Array.isArray(ids)) {
|
|
777
|
+
collections.value = items.filter((collection) => ids.indexOf(collection.id) !== -1);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
return {
|
|
781
|
+
collections,
|
|
782
|
+
load,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
function useCollection() {
|
|
786
|
+
const collection = vue.ref(null);
|
|
787
|
+
const load = async (id) => {
|
|
788
|
+
collection.value = null;
|
|
789
|
+
const items = await getCollections();
|
|
790
|
+
const index = items.findIndex((item) => item.id === id);
|
|
791
|
+
if (index !== -1) {
|
|
792
|
+
collection.value = items[index];
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
return {
|
|
796
|
+
collection,
|
|
797
|
+
load,
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function useCategoryFeaturesList(categoryId, errorHandler) {
|
|
802
|
+
return useListLoader(async function () {
|
|
803
|
+
if (!categoryId) {
|
|
804
|
+
return [];
|
|
805
|
+
}
|
|
806
|
+
return api.getCategoryFeatures(categoryId);
|
|
807
|
+
}, getApiErrorHandler(errorHandler, function (e) {
|
|
808
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось загрузить список фильтров.'));
|
|
809
|
+
}));
|
|
810
|
+
}
|
|
811
|
+
function useCategoryFeaturesGroupsList(categoryId, errorHandler) {
|
|
812
|
+
return useListLoader(async function () {
|
|
813
|
+
if (!categoryId) {
|
|
814
|
+
return [];
|
|
815
|
+
}
|
|
816
|
+
return api.getCategoryFeaturesGroups(categoryId);
|
|
817
|
+
}, getApiErrorHandler(errorHandler, function (e) {
|
|
818
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось загрузить список фильтров.'));
|
|
819
|
+
}));
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function useArticlesList(errorHandler) {
|
|
823
|
+
return usePaginatedLoader(api.getArticles, getApiErrorHandler(errorHandler, function (e) {
|
|
824
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось загрузить список статей.'));
|
|
825
|
+
}));
|
|
826
|
+
}
|
|
827
|
+
function useArticleCategoriesList(errorHandler) {
|
|
828
|
+
return usePaginatedLoader(api.getArticleCategories, getApiErrorHandler(errorHandler, function (e) {
|
|
829
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось загрузить список категорий статей.'));
|
|
830
|
+
}));
|
|
831
|
+
}
|
|
832
|
+
function useArticleItem(errorHandler) {
|
|
833
|
+
return useItemLoader(api.getArticle, getApiErrorHandler(errorHandler, function (e) {
|
|
834
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось загрузить статью.'));
|
|
835
|
+
}));
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
exports.AuthenticationStage = void 0;
|
|
839
|
+
(function (AuthenticationStage) {
|
|
840
|
+
AuthenticationStage["Check"] = "check";
|
|
841
|
+
AuthenticationStage["Login"] = "login";
|
|
842
|
+
AuthenticationStage["Register"] = "register";
|
|
843
|
+
AuthenticationStage["Confirmation"] = "confirmation";
|
|
844
|
+
})(exports.AuthenticationStage || (exports.AuthenticationStage = {}));
|
|
845
|
+
exports.AuthenticationEvent = void 0;
|
|
846
|
+
(function (AuthenticationEvent) {
|
|
847
|
+
AuthenticationEvent["Stage"] = "stage";
|
|
848
|
+
AuthenticationEvent["Completed"] = "completed";
|
|
849
|
+
})(exports.AuthenticationEvent || (exports.AuthenticationEvent = {}));
|
|
850
|
+
exports.AuthenticationResultType = void 0;
|
|
851
|
+
(function (AuthenticationResultType) {
|
|
852
|
+
AuthenticationResultType["Pending"] = "pending";
|
|
853
|
+
AuthenticationResultType["Validation"] = "validation";
|
|
854
|
+
AuthenticationResultType["Failed"] = "failed";
|
|
855
|
+
AuthenticationResultType["Completed"] = "completed";
|
|
856
|
+
AuthenticationResultType["Stage"] = "stage";
|
|
857
|
+
})(exports.AuthenticationResultType || (exports.AuthenticationResultType = {}));
|
|
858
|
+
|
|
859
|
+
function useAuthenticationStatusChecker(form, emit) {
|
|
860
|
+
const active = vue.ref(false);
|
|
861
|
+
const check = async () => {
|
|
862
|
+
if (active.value) {
|
|
863
|
+
return {
|
|
864
|
+
type: exports.AuthenticationResultType.Pending,
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
else if (!form.value.phone.number || !form.value.phone.country) {
|
|
868
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать номер телефона.');
|
|
869
|
+
return {
|
|
870
|
+
type: exports.AuthenticationResultType.Validation,
|
|
871
|
+
data: 'phone',
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
active.value = true;
|
|
875
|
+
try {
|
|
876
|
+
const status = await api.getAuthenticationStatus(form.value.phone);
|
|
877
|
+
if (!status.registered) {
|
|
878
|
+
emit(exports.AuthenticationEvent.Stage, exports.AuthenticationStage.Register);
|
|
879
|
+
return {
|
|
880
|
+
type: exports.AuthenticationResultType.Completed,
|
|
881
|
+
data: exports.AuthenticationStage.Register,
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
else if (!status.confirmed) {
|
|
885
|
+
emit(exports.AuthenticationEvent.Stage, exports.AuthenticationStage.Confirmation);
|
|
886
|
+
return {
|
|
887
|
+
type: exports.AuthenticationResultType.Completed,
|
|
888
|
+
data: exports.AuthenticationStage.Confirmation,
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
emit(exports.AuthenticationEvent.Stage, exports.AuthenticationStage.Login);
|
|
892
|
+
return {
|
|
893
|
+
type: exports.AuthenticationResultType.Completed,
|
|
894
|
+
data: exports.AuthenticationStage.Login,
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
catch (e) {
|
|
898
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось проверить статус регистрации. Повторите попытку.'));
|
|
899
|
+
}
|
|
900
|
+
finally {
|
|
901
|
+
active.value = false;
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
type: exports.AuthenticationResultType.Failed,
|
|
905
|
+
};
|
|
906
|
+
};
|
|
907
|
+
return {
|
|
908
|
+
active,
|
|
909
|
+
check,
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function useAuthenticationForm() {
|
|
914
|
+
const stage = vue.ref(exports.AuthenticationStage.Check);
|
|
915
|
+
const form = vue.ref({
|
|
916
|
+
phone: {
|
|
917
|
+
number: '',
|
|
918
|
+
country: '',
|
|
919
|
+
},
|
|
920
|
+
password: '',
|
|
921
|
+
confirmation_code: '',
|
|
922
|
+
});
|
|
923
|
+
const reset = () => {
|
|
924
|
+
form.value.phone.country = 'RU';
|
|
925
|
+
form.value.phone.number = '';
|
|
926
|
+
form.value.password = '';
|
|
927
|
+
form.value.confirmation_code = '';
|
|
928
|
+
stage.value = exports.AuthenticationStage.Check;
|
|
929
|
+
};
|
|
930
|
+
return {
|
|
931
|
+
stage,
|
|
932
|
+
form,
|
|
933
|
+
reset,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const useCustomerStore = pinia.defineStore({
|
|
938
|
+
id: 'zenky:storefront:customer',
|
|
939
|
+
state: () => ({
|
|
940
|
+
inclusions: '',
|
|
941
|
+
loading: false,
|
|
942
|
+
loaded: false,
|
|
943
|
+
token: null,
|
|
944
|
+
customer: null,
|
|
945
|
+
}),
|
|
946
|
+
actions: {
|
|
947
|
+
async initialize(inclusions) {
|
|
948
|
+
this.inclusions = inclusions;
|
|
949
|
+
const storage = getStorage();
|
|
950
|
+
this.token = storage.get('customer.token', null);
|
|
951
|
+
this.loaded = false;
|
|
952
|
+
if (!this.token) {
|
|
953
|
+
this.loaded = true;
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
return this.load();
|
|
957
|
+
},
|
|
958
|
+
async load() {
|
|
959
|
+
if (this.loading || !this.token) {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
this.loading = true;
|
|
963
|
+
try {
|
|
964
|
+
api.setAxiosToken(this.token);
|
|
965
|
+
this.customer = await api.getCustomerProfile(this.inclusions);
|
|
966
|
+
events.useCustomerLoaded().publish({ customer: this.customer });
|
|
967
|
+
return this.customer;
|
|
968
|
+
}
|
|
969
|
+
catch (e) {
|
|
970
|
+
api.setAxiosToken();
|
|
971
|
+
}
|
|
972
|
+
finally {
|
|
973
|
+
this.loading = false;
|
|
974
|
+
this.loaded = true;
|
|
975
|
+
}
|
|
976
|
+
return null;
|
|
977
|
+
},
|
|
978
|
+
async setToken(token) {
|
|
979
|
+
this.token = token;
|
|
980
|
+
const storage = getStorage();
|
|
981
|
+
storage.set('customer.token', token);
|
|
982
|
+
const customer = await this.load();
|
|
983
|
+
events.useCustomerAuthenticated().publish({ customer });
|
|
984
|
+
return customer;
|
|
985
|
+
},
|
|
986
|
+
logout() {
|
|
987
|
+
this.token = null;
|
|
988
|
+
this.customer = null;
|
|
989
|
+
const storage = getStorage();
|
|
990
|
+
storage.delete('customer.token');
|
|
991
|
+
api.setAxiosToken();
|
|
992
|
+
events.useCustomerLoggedOut().publish();
|
|
993
|
+
},
|
|
994
|
+
},
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
function useLogin(form, emit) {
|
|
998
|
+
const active = vue.ref(false);
|
|
999
|
+
const login = async () => {
|
|
1000
|
+
if (active.value) {
|
|
1001
|
+
return {
|
|
1002
|
+
type: exports.AuthenticationResultType.Pending,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
else if (!form.value.phone.number || !form.value.phone.country) {
|
|
1006
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать номер телефона.');
|
|
1007
|
+
return {
|
|
1008
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1009
|
+
data: 'phone',
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
else if (!form.value.password) {
|
|
1013
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать пароль.');
|
|
1014
|
+
return {
|
|
1015
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1016
|
+
data: 'password',
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
active.value = true;
|
|
1020
|
+
try {
|
|
1021
|
+
const { token } = await api.login({ number: form.value.phone.number, country: form.value.phone.country }, form.value.password);
|
|
1022
|
+
const { setToken } = useCustomerStore();
|
|
1023
|
+
const customer = await setToken(token);
|
|
1024
|
+
ui.useNotification('success', 'Успешный вход', 'Вы успешно вошли в ваш аккаунт!');
|
|
1025
|
+
emit(exports.AuthenticationEvent.Completed);
|
|
1026
|
+
return {
|
|
1027
|
+
type: exports.AuthenticationResultType.Completed,
|
|
1028
|
+
data: customer,
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
catch (e) {
|
|
1032
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Указан неправильный пароль.'));
|
|
1033
|
+
}
|
|
1034
|
+
finally {
|
|
1035
|
+
active.value = false;
|
|
1036
|
+
}
|
|
1037
|
+
return {
|
|
1038
|
+
type: exports.AuthenticationResultType.Failed,
|
|
1039
|
+
};
|
|
1040
|
+
};
|
|
1041
|
+
return {
|
|
1042
|
+
active,
|
|
1043
|
+
login,
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function useLogout() {
|
|
1048
|
+
const logout = () => {
|
|
1049
|
+
const store = useCustomerStore();
|
|
1050
|
+
store.logout();
|
|
1051
|
+
};
|
|
1052
|
+
return {
|
|
1053
|
+
logout,
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function pluralize(value, choices, prependWithValue = true) {
|
|
1058
|
+
const word1 = choices[0];
|
|
1059
|
+
const word2 = choices[1];
|
|
1060
|
+
const word5 = choices[2];
|
|
1061
|
+
if (!Number.isInteger(value)) {
|
|
1062
|
+
return prependWithValue ? `${value} ${word2}` : word2;
|
|
1063
|
+
}
|
|
1064
|
+
let n = value % 100;
|
|
1065
|
+
if (n >= 5 && n <= 20) {
|
|
1066
|
+
return prependWithValue ? `${value} ${word5}` : word5;
|
|
1067
|
+
}
|
|
1068
|
+
if (n > 19) {
|
|
1069
|
+
n %= 10;
|
|
1070
|
+
}
|
|
1071
|
+
if (n > 4 || n === 0) {
|
|
1072
|
+
return prependWithValue ? `${value} ${word5}` : word5;
|
|
1073
|
+
}
|
|
1074
|
+
if (n === 1) {
|
|
1075
|
+
return prependWithValue ? `${value} ${word1}` : word1;
|
|
1076
|
+
}
|
|
1077
|
+
return prependWithValue ? `${value} ${word2}` : word2;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const useTimer = (cooldown) => {
|
|
1081
|
+
const seconds = vue.ref(0);
|
|
1082
|
+
const label = vue.computed(() => pluralize(seconds.value, ['секунду', 'секунды', 'секунд']));
|
|
1083
|
+
let timerInterval;
|
|
1084
|
+
const start = () => {
|
|
1085
|
+
clearInterval(timerInterval);
|
|
1086
|
+
seconds.value = cooldown;
|
|
1087
|
+
timerInterval = setInterval(() => {
|
|
1088
|
+
seconds.value -= 1;
|
|
1089
|
+
if (seconds.value === 0) {
|
|
1090
|
+
clearInterval(timerInterval);
|
|
1091
|
+
}
|
|
1092
|
+
}, 1000);
|
|
1093
|
+
};
|
|
1094
|
+
const stop = () => {
|
|
1095
|
+
clearInterval(timerInterval);
|
|
1096
|
+
};
|
|
1097
|
+
return {
|
|
1098
|
+
seconds,
|
|
1099
|
+
label,
|
|
1100
|
+
start,
|
|
1101
|
+
stop,
|
|
1102
|
+
};
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
function useRequest(form, active) {
|
|
1106
|
+
const { seconds, label, start } = useTimer(60);
|
|
1107
|
+
const sent = vue.ref(false);
|
|
1108
|
+
const request = async () => {
|
|
1109
|
+
if (active.value) {
|
|
1110
|
+
return {
|
|
1111
|
+
type: exports.AuthenticationResultType.Pending,
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
else if (seconds.value > 0) {
|
|
1115
|
+
ui.useNotification('error', 'Ошибка', 'Нужно подождать перед повторной отправкой кода сброса пароля.');
|
|
1116
|
+
return {
|
|
1117
|
+
type: exports.AuthenticationResultType.Pending,
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
else if (!form.value.phone.number || !form.value.phone.country) {
|
|
1121
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать номер телефона.');
|
|
1122
|
+
return {
|
|
1123
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1124
|
+
data: 'phone',
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
active.value = true;
|
|
1128
|
+
try {
|
|
1129
|
+
await api.requestPasswordResetCode(form.value.phone);
|
|
1130
|
+
ui.useNotification('success', 'Код отправлен', 'Код сброса пароля был отправлен на ваш номер.');
|
|
1131
|
+
sent.value = true;
|
|
1132
|
+
start();
|
|
1133
|
+
return {
|
|
1134
|
+
type: exports.AuthenticationResultType.Completed,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
catch (e) {
|
|
1138
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Нужно подождать перед повторной отправкой кода сброса пароля.'));
|
|
1139
|
+
}
|
|
1140
|
+
finally {
|
|
1141
|
+
active.value = false;
|
|
1142
|
+
}
|
|
1143
|
+
return {
|
|
1144
|
+
type: exports.AuthenticationResultType.Failed,
|
|
1145
|
+
};
|
|
1146
|
+
};
|
|
1147
|
+
return {
|
|
1148
|
+
seconds,
|
|
1149
|
+
label,
|
|
1150
|
+
request,
|
|
1151
|
+
sent,
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function useReset(form, active, emit) {
|
|
1155
|
+
const reset = async () => {
|
|
1156
|
+
if (active.value) {
|
|
1157
|
+
return {
|
|
1158
|
+
type: exports.AuthenticationResultType.Pending,
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
else if (!form.value.phone.number || !form.value.phone.country) {
|
|
1162
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать номер телефона.');
|
|
1163
|
+
return {
|
|
1164
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1165
|
+
data: 'phone',
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
else if (!form.value.password) {
|
|
1169
|
+
ui.useNotification('error', 'Ошибка', 'Нужно придумать новый пароль.');
|
|
1170
|
+
return {
|
|
1171
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1172
|
+
data: 'password',
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
else if (!form.value.confirmation_code) {
|
|
1176
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать код из SMS.');
|
|
1177
|
+
return {
|
|
1178
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1179
|
+
data: 'confirmation_code',
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
active.value = true;
|
|
1183
|
+
try {
|
|
1184
|
+
const result = await api.resetPassword(form.value.phone, form.value.confirmation_code, form.value.password);
|
|
1185
|
+
const store = useCustomerStore();
|
|
1186
|
+
await store.setToken(result.token);
|
|
1187
|
+
ui.useNotification('success', 'Пароль изменён', 'Вы успешно изменили пароль и вошли в свой аккаунт.');
|
|
1188
|
+
emit(exports.AuthenticationEvent.Completed);
|
|
1189
|
+
return {
|
|
1190
|
+
type: exports.AuthenticationResultType.Completed,
|
|
1191
|
+
data: result.customer,
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
catch (e) {
|
|
1195
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Указан неправильный код сброса пароля.'));
|
|
1196
|
+
}
|
|
1197
|
+
finally {
|
|
1198
|
+
active.value = false;
|
|
1199
|
+
}
|
|
1200
|
+
return {
|
|
1201
|
+
type: exports.AuthenticationResultType.Failed,
|
|
1202
|
+
};
|
|
1203
|
+
};
|
|
1204
|
+
return {
|
|
1205
|
+
reset,
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
function usePasswordReset(form, emit) {
|
|
1209
|
+
const active = vue.ref(false);
|
|
1210
|
+
const { request, sent, seconds, label } = useRequest(form, active);
|
|
1211
|
+
const { reset } = useReset(form, active, emit);
|
|
1212
|
+
return {
|
|
1213
|
+
active,
|
|
1214
|
+
sent,
|
|
1215
|
+
seconds,
|
|
1216
|
+
label,
|
|
1217
|
+
request,
|
|
1218
|
+
reset,
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
function useRegistration(form, emit) {
|
|
1223
|
+
const active = vue.ref(false);
|
|
1224
|
+
const register = async () => {
|
|
1225
|
+
if (active.value) {
|
|
1226
|
+
return {
|
|
1227
|
+
type: exports.AuthenticationResultType.Pending,
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
else if (!form.value.phone.number || !form.value.phone.country) {
|
|
1231
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать номер телефона.');
|
|
1232
|
+
return {
|
|
1233
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1234
|
+
data: 'phone',
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
else if (!form.value.password) {
|
|
1238
|
+
ui.useNotification('error', 'Ошибка', 'Нужно придумать пароль.');
|
|
1239
|
+
return {
|
|
1240
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1241
|
+
data: 'password',
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
active.value = true;
|
|
1245
|
+
try {
|
|
1246
|
+
const result = await api.register(form.value.phone, form.value.password);
|
|
1247
|
+
if (!result.confirmation_required) {
|
|
1248
|
+
emit(exports.AuthenticationEvent.Completed);
|
|
1249
|
+
ui.useNotification('success', 'Успешная регистрация', 'Вы успешно зарегистрировались!');
|
|
1250
|
+
return {
|
|
1251
|
+
type: exports.AuthenticationResultType.Completed,
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
emit(exports.AuthenticationEvent.Stage, exports.AuthenticationStage.Confirmation);
|
|
1255
|
+
return {
|
|
1256
|
+
type: exports.AuthenticationResultType.Stage,
|
|
1257
|
+
data: exports.AuthenticationStage.Confirmation,
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
catch (e) {
|
|
1261
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Этот телефон уже зарегистрирован.'));
|
|
1262
|
+
}
|
|
1263
|
+
finally {
|
|
1264
|
+
active.value = false;
|
|
1265
|
+
}
|
|
1266
|
+
return {
|
|
1267
|
+
type: exports.AuthenticationResultType.Failed,
|
|
1268
|
+
};
|
|
1269
|
+
};
|
|
1270
|
+
return {
|
|
1271
|
+
active,
|
|
1272
|
+
register,
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
function useResend(form, active) {
|
|
1277
|
+
const { seconds, label, start } = useTimer(60);
|
|
1278
|
+
const resend = async () => {
|
|
1279
|
+
if (active.value) {
|
|
1280
|
+
return {
|
|
1281
|
+
type: exports.AuthenticationResultType.Pending,
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
else if (seconds.value > 0) {
|
|
1285
|
+
ui.useNotification('error', 'Ошибка', 'Нужно подождать перед повторной отправкой кода подтверждения.');
|
|
1286
|
+
return {
|
|
1287
|
+
type: exports.AuthenticationResultType.Pending,
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
else if (!form.value.phone.number || !form.value.phone.country) {
|
|
1291
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать номер телефона.');
|
|
1292
|
+
return {
|
|
1293
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1294
|
+
data: 'phone',
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
active.value = true;
|
|
1298
|
+
try {
|
|
1299
|
+
await api.resendConfirmationCode(form.value.phone);
|
|
1300
|
+
start();
|
|
1301
|
+
return {
|
|
1302
|
+
type: exports.AuthenticationResultType.Completed,
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
catch (e) {
|
|
1306
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Нужно подождать перед повторной отправкой кода подтверждения.'));
|
|
1307
|
+
}
|
|
1308
|
+
finally {
|
|
1309
|
+
active.value = false;
|
|
1310
|
+
}
|
|
1311
|
+
return {
|
|
1312
|
+
type: exports.AuthenticationResultType.Failed,
|
|
1313
|
+
};
|
|
1314
|
+
};
|
|
1315
|
+
return {
|
|
1316
|
+
seconds,
|
|
1317
|
+
label,
|
|
1318
|
+
start,
|
|
1319
|
+
resend,
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
function useConfirm(form, active, emit) {
|
|
1323
|
+
const confirm = async (withPassword = false) => {
|
|
1324
|
+
if (active.value) {
|
|
1325
|
+
return {
|
|
1326
|
+
type: exports.AuthenticationResultType.Pending,
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
else if (!form.value.phone.number || !form.value.phone.country) {
|
|
1330
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать номер телефона.');
|
|
1331
|
+
return {
|
|
1332
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1333
|
+
data: 'phone',
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
else if (!form.value.confirmation_code) {
|
|
1337
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать код из SMS.');
|
|
1338
|
+
return {
|
|
1339
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1340
|
+
data: 'confirmation_code',
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
else if (withPassword && !form.value.password) {
|
|
1344
|
+
ui.useNotification('error', 'Ошибка', 'Нужно указать новый пароль.');
|
|
1345
|
+
return {
|
|
1346
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1347
|
+
data: 'password',
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
active.value = true;
|
|
1351
|
+
try {
|
|
1352
|
+
const result = await api.confirm(form.value.phone, form.value.confirmation_code, withPassword ? form.value.password : null);
|
|
1353
|
+
const store = useCustomerStore();
|
|
1354
|
+
await store.setToken(result.token);
|
|
1355
|
+
ui.useNotification('success', 'Успешное подтверждение телефона', 'Вы успешно подтвердили телефон и вошли в свой аккаунт.');
|
|
1356
|
+
emit(exports.AuthenticationEvent.Completed);
|
|
1357
|
+
return {
|
|
1358
|
+
type: exports.AuthenticationResultType.Completed,
|
|
1359
|
+
data: result.customer,
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
catch (e) {
|
|
1363
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Указан неправильный код подтверждения.'));
|
|
1364
|
+
if (api.getApiErrorCode(e) === 'auth.registration.password_required') {
|
|
1365
|
+
return {
|
|
1366
|
+
type: exports.AuthenticationResultType.Validation,
|
|
1367
|
+
data: 'password',
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
finally {
|
|
1372
|
+
active.value = false;
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
type: exports.AuthenticationResultType.Failed,
|
|
1376
|
+
};
|
|
1377
|
+
};
|
|
1378
|
+
return {
|
|
1379
|
+
confirm,
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
const usePhoneConfirmation = (form, emit) => {
|
|
1383
|
+
const active = vue.ref(false);
|
|
1384
|
+
const { seconds, label, start, resend } = useResend(form, active);
|
|
1385
|
+
const { confirm } = useConfirm(form, active, emit);
|
|
1386
|
+
return {
|
|
1387
|
+
active,
|
|
1388
|
+
seconds,
|
|
1389
|
+
label,
|
|
1390
|
+
start,
|
|
1391
|
+
resend,
|
|
1392
|
+
confirm,
|
|
1393
|
+
};
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
function useDeliveryAddressesList(errorHandler) {
|
|
1397
|
+
return usePaginatedLoader(api.getCustomerAddresses, getApiErrorHandler(errorHandler, function (e) {
|
|
1398
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось загрузить адреса доставки.'));
|
|
1399
|
+
}));
|
|
1400
|
+
}
|
|
1401
|
+
function useDeliveryAddressDestroyer(errorHandler) {
|
|
1402
|
+
return useDataDestroyer(api.deleteAddress, getApiErrorHandler(errorHandler, function (e) {
|
|
1403
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось удалить адрес доставки.'));
|
|
1404
|
+
}));
|
|
1405
|
+
}
|
|
1406
|
+
function useEditAddressForm(address) {
|
|
1407
|
+
var _a, _b;
|
|
1408
|
+
const addressFields = address.resolver === 'plain' ? {
|
|
1409
|
+
id: address.id,
|
|
1410
|
+
name: address.name,
|
|
1411
|
+
city: (_a = address === null || address === void 0 ? void 0 : address.city) === null || _a === void 0 ? void 0 : _a.short,
|
|
1412
|
+
street: (_b = address === null || address === void 0 ? void 0 : address.street) === null || _b === void 0 ? void 0 : _b.short,
|
|
1413
|
+
house: address.house,
|
|
1414
|
+
block: address.block || '',
|
|
1415
|
+
apartment: address.apartment,
|
|
1416
|
+
entrance: address.entrance,
|
|
1417
|
+
floor: address.floor,
|
|
1418
|
+
has_intercom: address.has_intercom,
|
|
1419
|
+
} : {
|
|
1420
|
+
id: address.id,
|
|
1421
|
+
name: address.name,
|
|
1422
|
+
address: getExistedAddressDisplayValue(address, false),
|
|
1423
|
+
house: address.house,
|
|
1424
|
+
block: address.block || '',
|
|
1425
|
+
apartment: address.apartment,
|
|
1426
|
+
entrance: address.entrance,
|
|
1427
|
+
floor: address.floor,
|
|
1428
|
+
has_intercom: address.has_intercom,
|
|
1429
|
+
};
|
|
1430
|
+
const saving = vue.ref(false);
|
|
1431
|
+
const form = vue.ref(addressFields);
|
|
1432
|
+
const save = async () => {
|
|
1433
|
+
if (saving.value) {
|
|
1434
|
+
return false;
|
|
1435
|
+
}
|
|
1436
|
+
const { errors, data } = useAddressFormValue(form.value, address.resolver, address);
|
|
1437
|
+
if (errors.length > 0) {
|
|
1438
|
+
ui.useNotification('error', 'Ошибка', errors[0]);
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
saving.value = true;
|
|
1442
|
+
try {
|
|
1443
|
+
await api.updateAddress(address.id, {
|
|
1444
|
+
delivery_address: {
|
|
1445
|
+
...data,
|
|
1446
|
+
name: form.value.name,
|
|
1447
|
+
},
|
|
1448
|
+
});
|
|
1449
|
+
ui.useNotification('success', 'Адрес сохранён', 'Адрес был успешно сохранён!');
|
|
1450
|
+
return true;
|
|
1451
|
+
}
|
|
1452
|
+
catch (e) {
|
|
1453
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось сохранить адрес. Повторите попытку.'));
|
|
1454
|
+
}
|
|
1455
|
+
finally {
|
|
1456
|
+
saving.value = false;
|
|
1457
|
+
}
|
|
1458
|
+
return false;
|
|
1459
|
+
};
|
|
1460
|
+
return {
|
|
1461
|
+
form,
|
|
1462
|
+
saving,
|
|
1463
|
+
save,
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
function useCustomer() {
|
|
1468
|
+
const store = useCustomerStore();
|
|
1469
|
+
const customer = vue.computed(() => store.customer);
|
|
1470
|
+
const loaded = vue.computed(() => store.loaded);
|
|
1471
|
+
return {
|
|
1472
|
+
customer,
|
|
1473
|
+
loaded,
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
function useOrdersList(errorHandler) {
|
|
1478
|
+
return usePaginatedLoader(api.getCustomerOrders, getApiErrorHandler(errorHandler, function (e) {
|
|
1479
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось загрузить список заказов.'));
|
|
1480
|
+
}));
|
|
1481
|
+
}
|
|
1482
|
+
function useOrderItem(errorHandler) {
|
|
1483
|
+
return useItemLoader(api.getOrder, getApiErrorHandler(errorHandler, function (e) {
|
|
1484
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Заказ не найден.'));
|
|
1485
|
+
}));
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function usePaymentMethodsList(errorHandler) {
|
|
1489
|
+
return usePaginatedLoader(api.getCustomerPaymentMethods, getApiErrorHandler(errorHandler, function (e) {
|
|
1490
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось загрузить способы оплаты.'));
|
|
1491
|
+
}));
|
|
1492
|
+
}
|
|
1493
|
+
function usePaymentMethodDestroyer(errorHandler) {
|
|
1494
|
+
return useDataDestroyer(api.deleteCustomerPaymentMethod, getApiErrorHandler(errorHandler, function (e) {
|
|
1495
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось удалить способ оплаты.'));
|
|
1496
|
+
}));
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
function getFirstMedium(media, sizes = ['large', 'medium'], preferWebp = true) {
|
|
1500
|
+
const medium = Array.isArray(media) ? media[0] : media;
|
|
1501
|
+
if (!medium || !medium.images) {
|
|
1502
|
+
return null;
|
|
1503
|
+
}
|
|
1504
|
+
const sizesMap = {
|
|
1505
|
+
'placeholder': api.ImageType.Placeholder,
|
|
1506
|
+
'placeholder_webp': api.ImageType.PlaceholderWebp,
|
|
1507
|
+
'thumb': api.ImageType.Thumb,
|
|
1508
|
+
'thumb_webp': api.ImageType.ThumbWebp,
|
|
1509
|
+
'medium': api.ImageType.Medium,
|
|
1510
|
+
'medium_webp': api.ImageType.MediumWebp,
|
|
1511
|
+
'large': api.ImageType.Large,
|
|
1512
|
+
'large_webp': api.ImageType.LargeWebp,
|
|
1513
|
+
'xlarge': api.ImageType.XLarge,
|
|
1514
|
+
'xlarge_webp': api.ImageType.XLargeWebp,
|
|
1515
|
+
'hd': api.ImageType.HD,
|
|
1516
|
+
'hd_webp': api.ImageType.HDWebp,
|
|
1517
|
+
};
|
|
1518
|
+
const images = [];
|
|
1519
|
+
sizes.forEach((size) => {
|
|
1520
|
+
const webpImage = medium.images[sizesMap[`${size}_webp`]];
|
|
1521
|
+
if (preferWebp && webpImage) {
|
|
1522
|
+
images.push(webpImage);
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
const jpegImage = medium.images[sizesMap[size]];
|
|
1526
|
+
if (jpegImage) {
|
|
1527
|
+
images.push(jpegImage);
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
return images.length > 0 ? images[0] : null;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
function useProductsPricesRange(categoryId, errorHandler) {
|
|
1534
|
+
return useDataLoader(async () => api.getProductsPricesRange(categoryId), getApiErrorHandler(errorHandler, function (e) {
|
|
1535
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось загрузить диапазон цен.'));
|
|
1536
|
+
}));
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
function useFeaturesFilters(categoryId, values) {
|
|
1540
|
+
const loader = useCategoryFeaturesList(categoryId);
|
|
1541
|
+
const filters = vue.ref({});
|
|
1542
|
+
const load = async () => {
|
|
1543
|
+
await loader.load();
|
|
1544
|
+
loader.items.value.forEach((feature) => {
|
|
1545
|
+
filters.value[feature.alias] = values && values[feature.alias] ? values[feature.alias] : [];
|
|
1546
|
+
});
|
|
1547
|
+
};
|
|
1548
|
+
return {
|
|
1549
|
+
filters,
|
|
1550
|
+
load,
|
|
1551
|
+
items: loader.items,
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
function useFeaturesGroupsFilters(categoryId, values) {
|
|
1555
|
+
const loader = useCategoryFeaturesGroupsList(categoryId);
|
|
1556
|
+
const filters = vue.ref({});
|
|
1557
|
+
const load = async () => {
|
|
1558
|
+
await loader.load();
|
|
1559
|
+
loader.items.value.forEach((container) => {
|
|
1560
|
+
container.features.forEach((item) => {
|
|
1561
|
+
filters.value[item.feature.alias] = values && values[item.feature.alias] ? values[item.feature.alias] : [];
|
|
1562
|
+
});
|
|
1563
|
+
});
|
|
1564
|
+
};
|
|
1565
|
+
return {
|
|
1566
|
+
filters,
|
|
1567
|
+
load,
|
|
1568
|
+
items: loader.items,
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
function useProductsPricesRangeFilters(categoryId, values) {
|
|
1572
|
+
const loader = useProductsPricesRange(categoryId);
|
|
1573
|
+
const filters = vue.ref({
|
|
1574
|
+
min: (values === null || values === void 0 ? void 0 : values.min) || '',
|
|
1575
|
+
max: (values === null || values === void 0 ? void 0 : values.max) || '',
|
|
1576
|
+
});
|
|
1577
|
+
return {
|
|
1578
|
+
filters,
|
|
1579
|
+
load: async () => loader.load(),
|
|
1580
|
+
range: loader.data,
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
var ProductsSourceType;
|
|
1585
|
+
(function (ProductsSourceType) {
|
|
1586
|
+
ProductsSourceType["Category"] = "category";
|
|
1587
|
+
ProductsSourceType["Collection"] = "collection";
|
|
1588
|
+
})(ProductsSourceType || (ProductsSourceType = {}));
|
|
1589
|
+
var ProductsSorting;
|
|
1590
|
+
(function (ProductsSorting) {
|
|
1591
|
+
ProductsSorting["Manual"] = "manual";
|
|
1592
|
+
ProductsSorting["NameAsc"] = "name";
|
|
1593
|
+
ProductsSorting["NameDesc"] = "-name";
|
|
1594
|
+
ProductsSorting["PriceAsc"] = "price";
|
|
1595
|
+
ProductsSorting["PriceDesc"] = "-price";
|
|
1596
|
+
})(ProductsSorting || (ProductsSorting = {}));
|
|
1597
|
+
var ProductsFiltersType;
|
|
1598
|
+
(function (ProductsFiltersType) {
|
|
1599
|
+
ProductsFiltersType["Features"] = "features";
|
|
1600
|
+
ProductsFiltersType["FeaturesGroups"] = "featuresGroups";
|
|
1601
|
+
})(ProductsFiltersType || (ProductsFiltersType = {}));
|
|
1602
|
+
|
|
1603
|
+
function getSourceParams(source) {
|
|
1604
|
+
if (source === null || !source.id || !source.type) {
|
|
1605
|
+
return {};
|
|
1606
|
+
}
|
|
1607
|
+
switch (source.type) {
|
|
1608
|
+
case 'category':
|
|
1609
|
+
return { category_id: source.id };
|
|
1610
|
+
case 'collection':
|
|
1611
|
+
return { collection_id: source.id };
|
|
1612
|
+
default:
|
|
1613
|
+
return {};
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
function getSortingParams(sorting) {
|
|
1617
|
+
if (!sorting) {
|
|
1618
|
+
return {};
|
|
1619
|
+
}
|
|
1620
|
+
switch (sorting) {
|
|
1621
|
+
case ProductsSorting.Manual:
|
|
1622
|
+
case ProductsSorting.NameAsc:
|
|
1623
|
+
case ProductsSorting.NameDesc:
|
|
1624
|
+
case ProductsSorting.PriceAsc:
|
|
1625
|
+
case ProductsSorting.PriceDesc:
|
|
1626
|
+
return { order_by: sorting };
|
|
1627
|
+
default:
|
|
1628
|
+
return {};
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function getFiltersParams(filters) {
|
|
1632
|
+
if (typeof filters !== 'object' || !Object.keys(filters).length) {
|
|
1633
|
+
return {};
|
|
1634
|
+
}
|
|
1635
|
+
const features = {};
|
|
1636
|
+
Object.keys(filters).forEach((key) => {
|
|
1637
|
+
if (Array.isArray(filters[key]) && filters[key].length > 0) {
|
|
1638
|
+
features[key] = filters[key].join(',');
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
if (!Object.keys(features).length) {
|
|
1642
|
+
return {};
|
|
1643
|
+
}
|
|
1644
|
+
return { f: features };
|
|
1645
|
+
}
|
|
1646
|
+
function getPricesFiltersParams(filters) {
|
|
1647
|
+
const params = {};
|
|
1648
|
+
if (filters.min) {
|
|
1649
|
+
params.min_price = filters.min;
|
|
1650
|
+
}
|
|
1651
|
+
if (filters.max) {
|
|
1652
|
+
params.max_price = filters.max;
|
|
1653
|
+
}
|
|
1654
|
+
return params;
|
|
1655
|
+
}
|
|
1656
|
+
function getLoaderParams(page, loader) {
|
|
1657
|
+
const params = {
|
|
1658
|
+
page,
|
|
1659
|
+
count: loader.pagination.count,
|
|
1660
|
+
};
|
|
1661
|
+
if (loader.cityId) {
|
|
1662
|
+
params.city_id = loader.cityId;
|
|
1663
|
+
}
|
|
1664
|
+
if (loader.visibility) {
|
|
1665
|
+
params.visibility = loader.visibility;
|
|
1666
|
+
}
|
|
1667
|
+
if (loader.with) {
|
|
1668
|
+
params.with = loader.with;
|
|
1669
|
+
}
|
|
1670
|
+
if (loader.search) {
|
|
1671
|
+
params.search = loader.search;
|
|
1672
|
+
}
|
|
1673
|
+
return params;
|
|
1674
|
+
}
|
|
1675
|
+
function getProductsPaginationRequest(page, loader) {
|
|
1676
|
+
if (loader.source && typeof loader.source.error !== 'undefined' && loader.source.error) {
|
|
1677
|
+
return null;
|
|
1678
|
+
}
|
|
1679
|
+
return {
|
|
1680
|
+
...getSourceParams(loader.source),
|
|
1681
|
+
...getSortingParams(loader.sorting),
|
|
1682
|
+
...getFiltersParams(loader.filters),
|
|
1683
|
+
...getPricesFiltersParams(loader.pricesFilters),
|
|
1684
|
+
...getLoaderParams(page, loader),
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function getCategoryIdFromSource(source) {
|
|
1689
|
+
if (!source || !source.type || !source.id || source.error) {
|
|
1690
|
+
return null;
|
|
1691
|
+
}
|
|
1692
|
+
else if (source.type !== ProductsSourceType.Category) {
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
return source.id;
|
|
1696
|
+
}
|
|
1697
|
+
function useProductsList(config, errorHandler) {
|
|
1698
|
+
const productsLoader = usePaginatedLoader(api.getProducts, getApiErrorHandler(errorHandler, function (e) {
|
|
1699
|
+
ui.useNotification('error', 'Ошибка', api.getApiErrorMessage(e, 'Не удалось загрузить товары.'));
|
|
1700
|
+
}));
|
|
1701
|
+
const store = useStoreStore();
|
|
1702
|
+
const cityId = vue.computed(() => store.cityId);
|
|
1703
|
+
const sorting = vue.ref(config.value.sorting || ProductsSorting.Manual);
|
|
1704
|
+
const search = vue.ref(config.value.search || '');
|
|
1705
|
+
const { filters, load: loadFilters, items: features, } = config.value.filtersType === ProductsFiltersType.Features
|
|
1706
|
+
? useFeaturesFilters(getCategoryIdFromSource(config.value.source), config.value.filters)
|
|
1707
|
+
: useFeaturesGroupsFilters(getCategoryIdFromSource(config.value.source), config.value.filters);
|
|
1708
|
+
const { filters: pricesFilters, load: loadPricesRange, range, } = useProductsPricesRangeFilters(getCategoryIdFromSource(config.value.source), config.value.pricesFilters);
|
|
1709
|
+
const allowed = vue.ref(false);
|
|
1710
|
+
const loader = vue.computed(() => ({
|
|
1711
|
+
source: config.value.source,
|
|
1712
|
+
cityId: cityId.value,
|
|
1713
|
+
sorting: sorting.value,
|
|
1714
|
+
search: search.value,
|
|
1715
|
+
filters: filters.value,
|
|
1716
|
+
pricesFilters: pricesFilters.value,
|
|
1717
|
+
pagination: {
|
|
1718
|
+
count: config.value.count,
|
|
1719
|
+
},
|
|
1720
|
+
with: config.value.with || '',
|
|
1721
|
+
}));
|
|
1722
|
+
const load = async (page, reset = false, callback) => {
|
|
1723
|
+
const request = getProductsPaginationRequest(page, loader.value);
|
|
1724
|
+
if (request === null) {
|
|
1725
|
+
allowed.value = false;
|
|
1726
|
+
if (typeof callback === 'function') {
|
|
1727
|
+
callback(null);
|
|
1728
|
+
}
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
allowed.value = true;
|
|
1732
|
+
await productsLoader.load(request, reset);
|
|
1733
|
+
if (typeof callback === 'function') {
|
|
1734
|
+
callback(request);
|
|
1735
|
+
}
|
|
1736
|
+
};
|
|
1737
|
+
const init = async (callback) => {
|
|
1738
|
+
if (config.value.filtersType !== null) {
|
|
1739
|
+
await loadFilters();
|
|
1740
|
+
}
|
|
1741
|
+
await loadPricesRange();
|
|
1742
|
+
vue.watch(loader, lodashEs.debounce(async () => load(1, true, callback), 1000), {
|
|
1743
|
+
deep: true,
|
|
1744
|
+
});
|
|
1745
|
+
await load(1, true, callback);
|
|
1746
|
+
};
|
|
1747
|
+
return {
|
|
1748
|
+
state: productsLoader.state,
|
|
1749
|
+
pagination: productsLoader.pagination,
|
|
1750
|
+
items: productsLoader.items,
|
|
1751
|
+
allowed,
|
|
1752
|
+
filters,
|
|
1753
|
+
pricesFilters,
|
|
1754
|
+
sorting,
|
|
1755
|
+
features,
|
|
1756
|
+
search,
|
|
1757
|
+
pricesRange: range,
|
|
1758
|
+
init,
|
|
1759
|
+
load,
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
function getFiltersForUrl(filters) {
|
|
1763
|
+
const params = [];
|
|
1764
|
+
Object.keys(filters).forEach((key) => {
|
|
1765
|
+
params.push(`${key}:${filters[key]}`);
|
|
1766
|
+
});
|
|
1767
|
+
return params.join(';');
|
|
1768
|
+
}
|
|
1769
|
+
function getFiltersFromUrl(param) {
|
|
1770
|
+
const items = param.split(';');
|
|
1771
|
+
const filters = {};
|
|
1772
|
+
items.forEach((item) => {
|
|
1773
|
+
const [filter, value] = item.split(':');
|
|
1774
|
+
if (!filter || !value) {
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
filters[filter] = value.split(',');
|
|
1778
|
+
});
|
|
1779
|
+
return filters;
|
|
1780
|
+
}
|
|
1781
|
+
function getProductsListQueryParams(config, request) {
|
|
1782
|
+
if (request === null) {
|
|
1783
|
+
return {};
|
|
1784
|
+
}
|
|
1785
|
+
const query = {};
|
|
1786
|
+
if (request.order_by && request.order_by !== config.sorting) {
|
|
1787
|
+
query.order_by = request.order_by;
|
|
1788
|
+
}
|
|
1789
|
+
if (typeof request.f !== 'undefined' && Object.keys(request.f).length > 0) {
|
|
1790
|
+
query.f = getFiltersForUrl(request.f);
|
|
1791
|
+
}
|
|
1792
|
+
if (request.min_price) {
|
|
1793
|
+
query.min_price = request.min_price;
|
|
1794
|
+
}
|
|
1795
|
+
if (request.max_price) {
|
|
1796
|
+
query.max_price = request.max_price;
|
|
1797
|
+
}
|
|
1798
|
+
if (request.search) {
|
|
1799
|
+
query.search = request.search;
|
|
1800
|
+
}
|
|
1801
|
+
return query;
|
|
1802
|
+
}
|
|
1803
|
+
function getProductsListParamsFromQuery(query) {
|
|
1804
|
+
if (!query) {
|
|
1805
|
+
return null;
|
|
1806
|
+
}
|
|
1807
|
+
return {
|
|
1808
|
+
sorting: typeof query.order_by !== 'undefined' ? query.order_by : null,
|
|
1809
|
+
f: typeof query.f === 'string' ? getFiltersFromUrl(query.f) : null,
|
|
1810
|
+
min_price: typeof query.min_price !== 'undefined' && query.min_price ? query.min_price : null,
|
|
1811
|
+
max_price: typeof query.max_price !== 'undefined' && query.max_price ? query.max_price : null,
|
|
1812
|
+
search: typeof query.search !== 'undefined' && query.search ? query.search : null,
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
function getInjectedSource(sourceInjection, notFoundInjection, type) {
|
|
1817
|
+
const source = vue.inject(sourceInjection);
|
|
1818
|
+
const notFound = vue.inject(notFoundInjection);
|
|
1819
|
+
if (!source) {
|
|
1820
|
+
return null;
|
|
1821
|
+
}
|
|
1822
|
+
else if (notFound && notFound.value) {
|
|
1823
|
+
return {
|
|
1824
|
+
error: true,
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
return {
|
|
1828
|
+
type: type,
|
|
1829
|
+
id: source.value.id,
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
function getProductsSource(source) {
|
|
1833
|
+
if (source && source.type && source.id) {
|
|
1834
|
+
return source;
|
|
1835
|
+
}
|
|
1836
|
+
else if (source && source.error) {
|
|
1837
|
+
return null;
|
|
1838
|
+
}
|
|
1839
|
+
const injected = vue.inject(exports.ZenkyInjection.ProductsSource, null);
|
|
1840
|
+
if (!injected) {
|
|
1841
|
+
return null;
|
|
1842
|
+
}
|
|
1843
|
+
switch (injected) {
|
|
1844
|
+
case ProductsSourceType.Category:
|
|
1845
|
+
return getInjectedSource(exports.ZenkyInjection.Category, exports.ZenkyInjection.CategoryNotFound, injected);
|
|
1846
|
+
case ProductsSourceType.Collection:
|
|
1847
|
+
return getInjectedSource(exports.ZenkyInjection.Collection, exports.ZenkyInjection.CollectionNotFound, injected);
|
|
1848
|
+
default:
|
|
1849
|
+
return null;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
exports.createStorage = createStorage;
|
|
1854
|
+
exports.getAddressForm = getAddressForm;
|
|
1855
|
+
exports.getApiErrorHandler = getApiErrorHandler;
|
|
1856
|
+
exports.getExistedAddress = getExistedAddress;
|
|
1857
|
+
exports.getExistedAddressDisplayValue = getExistedAddressDisplayValue;
|
|
1858
|
+
exports.getFirstMedium = getFirstMedium;
|
|
1859
|
+
exports.getProductsListParamsFromQuery = getProductsListParamsFromQuery;
|
|
1860
|
+
exports.getProductsListQueryParams = getProductsListQueryParams;
|
|
1861
|
+
exports.getProductsPaginationRequest = getProductsPaginationRequest;
|
|
1862
|
+
exports.getProductsSource = getProductsSource;
|
|
1863
|
+
exports.getSelectedAddress = getSelectedAddress;
|
|
1864
|
+
exports.getSelectedAddressDisplayValue = getSelectedAddressDisplayValue;
|
|
1865
|
+
exports.getStockAddress = getStockAddress;
|
|
1866
|
+
exports.getStockSchedule = getStockSchedule;
|
|
1867
|
+
exports.getStorage = getStorage;
|
|
1868
|
+
exports.useAddressFormValue = useAddressFormValue;
|
|
1869
|
+
exports.useArticleCategoriesList = useArticleCategoriesList;
|
|
1870
|
+
exports.useArticleItem = useArticleItem;
|
|
1871
|
+
exports.useArticlesList = useArticlesList;
|
|
1872
|
+
exports.useAuthenticationForm = useAuthenticationForm;
|
|
1873
|
+
exports.useAuthenticationStatusChecker = useAuthenticationStatusChecker;
|
|
1874
|
+
exports.useCategories = useCategories;
|
|
1875
|
+
exports.useCategoriesStore = useCategoriesStore;
|
|
1876
|
+
exports.useCategoriesTree = useCategoriesTree;
|
|
1877
|
+
exports.useCategory = useCategory;
|
|
1878
|
+
exports.useCategoryFeaturesGroupsList = useCategoryFeaturesGroupsList;
|
|
1879
|
+
exports.useCategoryFeaturesList = useCategoryFeaturesList;
|
|
1880
|
+
exports.useCollection = useCollection;
|
|
1881
|
+
exports.useCollections = useCollections;
|
|
1882
|
+
exports.useCollectionsStore = useCollectionsStore;
|
|
1883
|
+
exports.useCustomer = useCustomer;
|
|
1884
|
+
exports.useCustomerStore = useCustomerStore;
|
|
1885
|
+
exports.useDadata = useDadata;
|
|
1886
|
+
exports.useDataDestroyer = useDataDestroyer;
|
|
1887
|
+
exports.useDataLoader = useDataLoader;
|
|
1888
|
+
exports.useDeliveryAddressDestroyer = useDeliveryAddressDestroyer;
|
|
1889
|
+
exports.useDeliveryAddressesList = useDeliveryAddressesList;
|
|
1890
|
+
exports.useEditAddressForm = useEditAddressForm;
|
|
1891
|
+
exports.useFeaturesFilters = useFeaturesFilters;
|
|
1892
|
+
exports.useFeaturesGroupsFilters = useFeaturesGroupsFilters;
|
|
1893
|
+
exports.useFlatCategoriesTree = useFlatCategoriesTree;
|
|
1894
|
+
exports.useItemLoader = useItemLoader;
|
|
1895
|
+
exports.useListLoader = useListLoader;
|
|
1896
|
+
exports.useLogin = useLogin;
|
|
1897
|
+
exports.useLogout = useLogout;
|
|
1898
|
+
exports.useOrderItem = useOrderItem;
|
|
1899
|
+
exports.useOrdersList = useOrdersList;
|
|
1900
|
+
exports.usePaginatedLoader = usePaginatedLoader;
|
|
1901
|
+
exports.usePasswordReset = usePasswordReset;
|
|
1902
|
+
exports.usePaymentMethodDestroyer = usePaymentMethodDestroyer;
|
|
1903
|
+
exports.usePaymentMethodsList = usePaymentMethodsList;
|
|
1904
|
+
exports.usePhoneConfirmation = usePhoneConfirmation;
|
|
1905
|
+
exports.useProductsList = useProductsList;
|
|
1906
|
+
exports.useProductsPricesRange = useProductsPricesRange;
|
|
1907
|
+
exports.useProductsPricesRangeFilters = useProductsPricesRangeFilters;
|
|
1908
|
+
exports.useRegistration = useRegistration;
|
|
1909
|
+
exports.useReversedTree = useReversedTree;
|
|
1910
|
+
exports.useSplittedCategoriesTree = useSplittedCategoriesTree;
|
|
1911
|
+
exports.useStateDataLoader = useStateDataLoader;
|
|
1912
|
+
exports.useStoreStore = useStoreStore;
|
|
1913
|
+
exports.useSuggestionsRestrictedToCity = useSuggestionsRestrictedToCity;
|