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