PageSourceSearch

https://beautysociety.com/wp-content/plugins/beautysociety-wrapper…ty-product-listing-directive.js?ver=6.0.133

js beautysociety.com collected 2026-10-01 12:36:02 UTC 37,208 bytes, 736 lines download raw bytes

1(function() {
2    'use strict';
3
4    const app = angular.module('app');
5
6    angular.module('app').run(['$rootScope', '$location', '$q', '$timeout', 'shoppingCartModel', 'idstcApiRequests', 'cartService', 'beautysocietyCommonService', 'beautysocietyToastMessageService', 'beautysocietyPosthogService', 'appService', function ($rootScope, $location, $q, $timeout, shoppingCartModel, idstcApiRequests, cartService, beautysocietyCommonService, beautysocietyToastMessageService, beautysocietyPosthogService, appService) {
7
8        // Track previous URL parameters to detect if only sort/page changed
9        let previousUrlParams = {
10            path: $location.path(),
11            search: $location.search()?.search,
12            sort: $location.search()?.sort,
13            pg: $location.search()?.pg
14        };
15
16        $rootScope.$on('$locationChangeStart', function () {
17            $(document).progressStart();
18        });
19
20        $rootScope.$on('$locationChangeSuccess', async function () {
21            const orderUrlParameter = getOrderUrlParameter($location);
22            const searchValue = $location.search()?.search;
23            const currentPath = $location.path();
24            const currentSort = $location.search()?.sort;
25            const currentPg = $location.search()?.pg;
26
27            // Check for join cart and handle separation from customer cart ONLY when on order page
28            if (orderUrlParameter !== -1) {
29                // Clear join cart and switch to customer cart when navigating to order page
30                cartService.clearJoinCartAndSwitchToCustomerCart();
31            }
32
33            // Check if only sort or page parameter changed (not category or search)
34            const onlySortOrPageChanged = 
35                previousUrlParams.path === currentPath &&
36                previousUrlParams.search === searchValue &&
37                (previousUrlParams.sort !== currentSort || previousUrlParams.pg !== currentPg);
38
39            // Update previous URL parameters
40            previousUrlParams = {
41                path: currentPath,
42                search: searchValue,
43                sort: currentSort,
44                pg: currentPg
45            };
46
47            // If only sort or page changed, skip reload to preserve filtered products — unless
48            // there is nothing left to preserve, which means something cleared the list and the
49            // shopper would be left looking at "No products found in this category".
50            if (onlySortOrPageChanged && shoppingCartModel.products?.length) {
51                $(document).progressFinish();
52                return;
53            }
54
55            // If not on order page and no search, don't load products
56            if (orderUrlParameter === -1 && !searchValue) {
57                $(document).progressFinish();
58                return;
59            }
60
61            beautysocietyCommonService.setProductsLoaded(false);
62
63            await updateCategoryFromUrl(
64                orderUrlParameter === -1 ? undefined : orderUrlParameter,
65                searchValue
66            );
67        });
68
69        function getOrderUrlParameter($location) {
70            const pathParts = $location.path().split('/').filter(part => part.length > 0);
71            const orderIndex = pathParts.findIndex(part => part.toLowerCase() === 'order');
72            
73            if (orderIndex === -1) {
74                return -1;
75            }
76
77            // Return the next part after 'order', or undefined if there isn't one
78            return pathParts[orderIndex + 1] || undefined;
79        }
80
81        async function loadCategories() {
82            const stored = sessionStorage.getItem('displayCategories');
83
84            if (stored) {
85                return beautysocietyCommonService.getFlattenedCategoriesList(JSON.parse(stored));
86            }
87
88            try {
89                const response = await idstcApiRequests.getDisplayCategories();
90
91                sessionStorage.setItem('displayCategories', JSON.stringify(response.data));
92
93                return beautysocietyCommonService.getFlattenedCategoriesList(response.data);;
94            } catch(error) {
95                return [];
96            }
97        }
98
99        async function updateCategoryFromUrl(urlExtension, searchValue) {
100            let categories = await loadCategories();
101            
102            // If categories aren't loaded yet (race condition), wait briefly and retry once
103            if ((!categories || !categories.length) && !searchValue) {
104                await new Promise(resolve => setTimeout(resolve, 150));
105                categories = await loadCategories();
106            }
107
108            // Even if categories fail to load, still load products with default category
109            const normalizedExtension = urlExtension?.toUpperCase()?.replace('%20', ' ') || '';
110
111            const category = categories?.find(cat => 
112                cat.URLExtension?.toUpperCase() === normalizedExtension
113            );
114
115            const selectedCategoryId = category ? category.DisplayCategoryID : (shoppingCartModel.selectedCategoryId || null);
116            const finalCategory = category || (selectedCategoryId && c
116ategories?.length ? categories.find(cat => cat.DisplayCategoryID === selectedCategoryId) : null);
117
118            clearSearchFilters(finalCategory);
119
120            try {
121                const cartId = await cartService.getCartId();
122
123                const searchParams = {
124                    CartID: cartId,
125                    CorporateConsultantURL: idstcSettingCorporateConsultantUrl,
126                    LanguageCode: beautysocietyCommonService.getLanguageCode()
127                };
128
129                if (searchValue) {
130                    searchParams.SearchValue = searchValue;
131
132                    beautysocietyPosthogService.trackEvent('Products Searched', {
133                        query: searchValue
134                    });
135                } else if (normalizedExtension?.toLowerCase() !== 'all-products' && normalizedExtension?.toLowerCase() !== 'allproducts') {
136                    if (!urlExtension) {
137                        // Default to category ID 1 (all products) when no category specified
138                        searchParams.DisplayCategoryID = 1;
139                    } else if (finalCategory) {
140                        searchParams.DisplayCategoryID = finalCategory.DisplayCategoryID;
141                    } else if (selectedCategoryId) {
142                        searchParams.DisplayCategoryID = selectedCategoryId;
143                    } else {
144                        // Default to category ID 1 instead of invalid ID
145                        searchParams.DisplayCategoryID = 999999;
146                    }
147                } else if(normalizedExtension?.toLowerCase() === 'all-products' || normalizedExtension?.toLowerCase() === 'allproducts') {
148                    searchParams.DisplayCategoryID = 1;
149                }
150                
151                const categoryProducts = await idstcApiRequests.getProductsForCart(searchParams);
152
153                beautysocietyCommonService.selectedCategory = finalCategory;
154    
155                shoppingCartModel.products = categoryProducts?.data;
156                shoppingCartModel.selectedCategory = finalCategory;
157                shoppingCartModel.selectedCategoryId = finalCategory ? finalCategory.DisplayCategoryID : selectedCategoryId;
158                shoppingCartModel.flatCategoriesList = categories;
159                shoppingCartModel.searchText = searchValue;
160
161                beautysocietyPosthogService.productListViewedEvent(null, finalCategory?.Name, categoryProducts?.data);
162                
163                $timeout(function() {
164                    $rootScope.beautysocietyProducts = categoryProducts?.data ? [...categoryProducts.data] : [];
165                }, 0);
166            } catch(error) {
167                beautysocietyToastMessageService.show(error?.message || 'An error occurred while loading the products.', 'error');
168            } finally {
169                $(document).progressFinish();
170                shoppingCartModel.cartItemsUpdating = false;
171
172                // "No products found" hangs off this flag, so it is only raised once the fetch has
173                // finished — and after the products above have been published, or the message would
174                // show for the tick in between.
175                $timeout(function() {
176                    beautysocietyCommonService.setProductsLoaded(true);
177                }, 0);
178            }
179        }
180
181        function clearSearchFilters(category) {
182            sessionStorage.removeItem('idstcCartFilter_SearchText');
183            sessionStorage.removeItem('idstcCartFilterOptions');
184            sessionStorage.setItem('idstcCartFilter_DisplayCategoryID', category ? category.DisplayCategoryID : null);
185            
186            shoppingCartModel.filterOptions = null;
187            shoppingCartModel.unfilteredProducts = null;
188        }
189    }]);
190
191    app.directive('beautisocietyProductListing', function ($q, $location, appService, cartService, shoppingCartModel) {
192        return {
193            restrict: 'E',
194            replace: true,
195            templateUrl: beautysocietyDirectivePluginRelativeUrl + '/assets/views/directives/beautysociety-product-listing-directive.html',
196            controller: function ($scope, $rootScope, $sce, beautysocietyCommonService, $timeout, idstcApiRequests, beautysocietyToastMessageService, beautysocietyPosthogService) {
197                $scope.vm = {};
198                $scope.dataLoaded = false;
199                $scope.isLoading = false;
200                $scope.products = [];
201                $scope.defaultImageUrl = beautysocietyDirectivePluginBaseUrl + '/assets/images/placeholder.jpg';
202                $scope.Math = window.Math;
203                $scope.beautysocietyCommonService = beautysocietyCommonService;
204
205                const urlPage = $location.search()?.pg;
206                const cookiePage = parseInt(getCookie('idstcItemCurrentPage') || 1);
207
208                $scope.currentPage = urlPage ? parseInt(urlPage) : cookiePage;
209                $scope.itemsPerPage = 24;
210                $scope.totalItems = 0;
211                $scope.totalPages = 0;
212                $scope.paginatedProducts = [];
213
214                $scope.vm.cartview = localStorage.getItem('idstcItemView') || (typeof getCookie === 'function' ? getCookie('idstcItemView') : null) || 'grid.html';
215                $scope.vm.shoppingCartModel = shoppingCartModel;
216                $scope.vm.sortByOptions = [{
217                    name: 'displayOrder',
218                    displayName: 'Most Popular',
219                    sort: 10
220                }, {
221                    name: 'price',
222                    displayName: 'Price Low to High',
223                    sort: 20
224                }, {
225                    name: '-price',
226                    displayName: 'Price High to Low',
227                    sort: 30
228                }, {
229                    name: 'displayName',
230                    displayName: 'Item Name A-Z',
231                    sort: 40
232                }];
233                
234                if (!$scope.vm.shoppingCartModel.sortExpression) {
235                    const urlSort = $location.search().sort;
236                    const storedSort = localStorage.getItem('idstcItemSortExpression') || (typeof getCookie === 'function' ? getCookie('idstcItemSortExpression') : null);
237                    $scope.vm.shoppingCartModel.sortExpression = urlSort || storedSort || 'displayOrder';
238                    shoppingCartModel.sortExpression = $scope.vm.shoppingCartModel.sortExpression;
239                }
240                
241                // Ensure sortExpression is always set (like idstc-acuta does)
242                if (!$scope.vm.shoppingCartModel.sortExpression || $scope.vm.shoppingCartModel.sortExpression === '') {
243                    $scope.vm.shoppingCartModel.sortExpression = 'displayOrder';
244                    shoppingCartModel.sortExpression = 'displayOrder';
245                }
246
247                // Sort products based on sortExpression
248                $scope.sortProducts = function() {
249                    if (!$scope.products || $scope.products.length === 0) {
250                        return;
251                    }
252
253                    const sortExpression = $scope.vm.shoppingCartModel.sortExpression || 'displayOrder';
254                    const productsCopy = angular.copy($scope.products);
255
256                    switch (sortExpression) {
257                        case 'price':
258                            productsCopy.sort(function (a, b) {
259                                if (a.price > b.price) {
260                                    return 1;
261                                } else if (a.price < b.price) {
262                                    return -1;
263                                }
264                                return 0;
265                            });
266                            break;
267                        case '-price':
268                            productsCopy.sort(function (a, b) {
269                                if (a.price < b.price) {
270                                    return 1;
271                                } else if (a.price > b.price) {
272                                    return -1;
273                                }
274                                return 0;
275                            });
276                            break;
277                        case 'displayName':
278                            productsCopy.sort(function (a, b) {
279                                const nameA = (a.displayName || a.title || '').toLowerCase();
280                                const nameB = (b.displayName || b.title || '').toLowerCase();
281                                if (nameA > nameB) {
282                                    return 1;
283                                } else if (nameA < nameB) {
284                                    return -1;
285                                }
286                                return 0;
287                            });
288                            break;
289                        case 'displayOrder':
290                        default:
291                            // Match idstc-acuta sorting: (a.displayOrder - b.displayOrder || a.displayName.localeCompare(b.displayName))
292                            productsCopy.sort(function (a, b) {
293                                const orderA = a.displayOrder || a.DisplayOrder || 0;
294                                const orderB = b.displayOrder || b.DisplayOrder || 0;
295                                
296                                if (orderA !== orderB) {
297                                    return orderA - orderB;
298                                }
299                                
300                                // If displayOrder is the same, sort by displayName
301                                const nameA = a.displayName || a.title || '';
302                                const nameB = b.displayName || b.title || '';
303                                return nameA.localeCompare(nameB);
304                            });
305                            break;
306                    }
307
308                    $scope.products = productsCopy;
309                };
310
311                // Pagination functions
312                $scope.updatePagination = function() {
313                    $scope.sortProducts();
314                    
315                    $scope.totalItems = $scope.products.length;
316                    $scope.totalPages = Math.ceil($scope.totalItems / $scope.itemsPerPage);
317                    
318                    // Reset to page 1 if current page is beyond total pages
319                    if ($scope.currentPage > $scope.totalPages && $scope.totalPages > 0) {
320                        $scope.currentPage = 1;
321                    }
322                    
323                    // Calculate start and end indices
324                    const startIndex = ($scope.currentPage - 1) * $scope.itemsPerPage;
325                    const endIndex = startIndex + $scope.itemsPerPage;
326                    
327                    // Get paginated products
328                    $scope.paginatedProducts = $scope.products.slice(startIndex, endIndex);
329                    
330                    // Initialize badge rotation after pagination update
331                    $timeout(function() {
332                        initializeBadgeRotation();
333                    }, 100);
334                };
335
336                $scope.goToPage = function(page) {
337                    if (page >= 1 && page <= $scope.totalPages) {
338                        $scope.currentPage = page;
339
340                        $location.search('pg', page);
341                        $scope.updatePagination();
342                        beautysocietyCommonService.scrollToTop();
343                    }
344                };
345
346                $scope.getPageNumbers = function() {
347                    const pages = [];
348                    const startPage = Math.max(1, $scope.currentPage - 2);
349                    const endPage = Math.min($scope.totalPages, $scope.currentPage + 2);
350                    
351                    for (let i = startPage; i <= endPage; i++) {
352                        pages.push(i);
353                    }
354                    
355                    return pages;
356                };
357
358                const loadProducts = function(products) {
359                    $scope.isLoading = false;
360                    
361                    // Rendering an empty list is not the same as having loaded nothing: this runs on
362                    // init too, before the first fetch has even been made. Whether products are
363                    // loaded is decided by the fetch itself.
364                    if (!products || !products.length) {
365                        $scope.products = [];
366                        shoppingCartModel.products = [];
367                        $scope.vm.shoppingCartModel = shoppingCartModel;
368                        $scope.updatePagination();
369                        $scope.setDataLoaded(true);
370
371                        beautysocietyCommonService.applyScope($scope);
372
373                        return;
374                    }
375
376                    $scope.products = beautysocietyCommonService.formatProducts(products);
377
378                    beautysocietyCommonService.applyScope($scope);
379
380                    shoppingCartModel.products = $scope.products;
381                    
382                    $scope.vm.shoppingCartModel = shoppingCartModel;
383
384                    // Restore user's sort preference from storage when products are loaded
385                    const storedSort = localStorage.getItem('idstcItemSortExpression') || (typeof getCookie === 'function' ? getCookie('idstcItemSortExpression') : null) || 'displayOrder';
386                    $scope.vm.shoppingCartModel.sortExpression = storedSort;
387                    shoppingCartModel.sortExpression = storedSort;
388
389                    // updatePagination will call sortProducts() to sort the products
390                    $scope.updatePagination();
391
392                    $rootScope.$broadcast('beautySocietyComponentMessage', {
393                        action: 'beautysocietyFilterCategoriesLoaded'
394                    });
395
396                    $scope.setDataLoaded(true);
397
398                    beautysocietyCommonService.applyScope($scope);
399                    
400                    // Initialize badge rotation for products with multiple badges
401                    $timeout(function() {
402                        initializeBadgeRotation();
403                    }, 500);
404                };
405              
406                // Initialize badge rotation animation
407                function initializeBadgeRotation() {
408                    const badgeContainers = document.querySelectorAll('.product-listing-badges-container');
409                    
410                    badgeContainers.forEach(function(container) {
411                        const badges = container.querySelectorAll('.product-listing-badge-item');
412                        if (badges.length <= 1) {
413                            return; // No rotation needed for single badge
414                        }
415                        
416                        let currentIndex = 0;
417                        
418                        function rotateBadge() {
419                            // Hide all badges
420                            badges.forEach(function(badge, index) {
421                                badge.classList.remove('badge-active');
422                                badge.classList.add('badge-hidden');
423                            });
424                            
425                            // Show current badge
426                            badges[currentIndex].classList.remove('badge-hidden');
427                            badges[currentIndex].classList.add('badge-active');
428                            
429                            // Move to next badge
430                            currentIndex = (currentIndex + 1) % badges.length;
431                        }
432                        
433                        // Rotate every 3 seconds
434                        const rotationInterval = setInterval(rotateBadge, 3000);
435                        
436                        // Store interval ID on container for cleanup if needed
437                        container.setAttribute('data-rotation-interval', rotationInterval);
438                    });
439                }
440              
441                $scope.navigateToOrderPage = () => {
442                    window.location = '/order';
443                };
444
445                $scope.trackProductClick = function(product, event) {
446                    beautysocietyCommonService.trackProductClick({
447                        ProductID: product?.productID,
448                        ProductSKU: product?.sku,
449                        DisplayCategoryID: beautysocietyCommonService?.selectedCategory?.DisplayCategoryID,
450                        ProductDisplayName: product?.displayName,
451                        GoogleBrand: product?.googleBrand,
452                        ProductVariation: product?.variationGroupID,
453                        Price: product?.price,
454                        QuantityAvailable: product?.quantityAvailable,
455                        CouponCode: product?.couponCode,
456                        CartUrl: product?.cartUrl
457                    }, event);
458                };
459
460                $scope.vm.sortChanged = function() {
461                    const sortExpression = $scope.vm.shoppingCartModel.sortExpression;
462                    localStorage.setItem('idstcItemSortExpression', sortExpression);
463                    $location.search('sort', sortExpression);
464                    $location.search('pg', 1);
465                    $scope.currentPage = 1;
466                    $scope.updatePagination();
467                    beautysocietyCommonService.scrollToTop();
468                };
469
470                const deregisterProductsWatch = $rootScope.$watch('beautysocietyProducts', function(newValue, oldValue) {
471                    if (newValue !== oldValue) {
472                        loadProducts(newValue || []);
473                    }
474                });
475
476                // Register event listener for other messages (not beautysocietyProductsUpdated)
477                const deregisterMessageListener = $rootScope.$on('beautySocietyComponentMessage', function(event, data) {
478                    if (data?.action === 'beautysocietyProductsFiltered') {
479                        $scope.setDataLoaded(false);
480                        $scope.products = beautysocietyCommonService.formatProducts(data?.products);
481                        $scope.currentPage = 1;
482                        $scope.updatePagination();
483                        $scope.setDataLoaded(true);
484                        beautysocietyCommonService.scrollToTop();
485                        beautysocietyPosthogService.productListFilteredEvent(null, beautysocietyCommonService?.selectedCategory?.Name, data?.selectedFilterOptions, $scope.vm.sortByOptions.find(s => s.name === $scope.vm.shoppingCartModel?.sortExpression) || null, data?.products);
486                    } else if (data?.action === 'clearAllFilters') {
487                        $scope.setDataLoaded(false);
488                        
489                        const originalProducts = $scope.vm.shoppingCartModel.unfilteredProducts || $scope.vm.shoppingCartModel.products;
490                        $scope.products = beautysocietyCommonService.formatProducts(originalProducts);
491                        $scope.currentPage = 1;
492                        $scope.updatePagination();
493                        $scope.setDataLoaded(true);
494                        beautysocietyCommonService.scrollToTop();
495                        beautysocietyPosthogService.productListFilteredEvent(null, beautysocietyCommonService?.selectedCategory?.Name, data?.selectedFilterOptions, $scope.vm.sortByOptions.find(s => s.name === $scope.vm.shoppingCartModel?.sortExpression) || null, []);
496                    } else if (data?.action === 'clearFilterOption') {
497                        const hasRemainingFilters = $rootScope.beautysocietyFilterOptions?.some(category => 
498                            category.Options?.some(option => option.Selected)
499                        );
500                        
501                        if (hasRemainingFilters) {
502                            // Prevent infinite recursion by using a different action
503                            $rootScope.$broadcast('beautySocietyComponentMessage', {
504                                action: 'applyRemainingFilters'
505                            });
506                        } else {
507                            $scope.setDataLoaded(false);
508                            const originalProducts = $scope.vm.shoppingCartModel.unfilteredProducts || $scope.vm.shoppingCartModel.products;
509                            $scope.products = beautysocietyCommonService.formatProducts(originalProducts);
510                            $scope.currentPage = 1;
511                            $scope.updatePagination();
512                            $scope.setDataLoaded(true);
513                            beautysocietyCommonService.scrollToTop();
514                            beautysocietyPosthogService.productListFilteredEvent(null, beautysocietyCommonService?.selectedCategory?.Name, data?.filterOption ? [data?.f
514ilterOption] : [], $scope.vm.sortByOptions.find(s => s.name === $scope.vm.shoppingCartModel?.sortExpression) || null, $scope.products);
515                        }
516                    } else if (data?.action === 'dataLoaded') {
517                        $scope.setDataLoaded(data?.value);
518                    }
519                });
520
521                // Clean up event listener and watch when scope is destroyed to prevent memory leaks
522                $scope.$on('$destroy', function() {
523                    if (deregisterMessageListener) {
524                        deregisterMessageListener();
525                    }
526                    if (deregisterProductsWatch) {
527                        deregisterProductsWatch();
528                    }
529                });
530
531                $scope.addToCart = (product) => {
532                    if (product.quantityAvailable > 0 && product.quantitySafelyAvailable <= 0 && product.safetyStockShowWarning && idstcSettingAcutaCartConfirmAddWhenBelowSafetyStock === 'on' || idstcSettingAcutaCartConfirmAddWhenBelowSafetyStock === 'true') {
533                        $scope.showSafetyStockWarningDialog(product);
534                        return;
535                    }
536                
537                    $scope.addToCartServer(product);
538                };
539
540                $scope.showSafetyStockWarningDialog = (product) => {
541                    const warningMessage = $scope.prepareSafetyStockWarning(product);
542                    
543                    if (warningMessage) {
544                        const continueText = 'Do you wish to continue adding this item to the cart?';
545                        const confirmAddText = warningMessage + '</br></br>' + continueText;
546                        
547                        idstc().dialog({
548                            message: confirmAddText,
549                            title: 'Confirm add to cart'
550                        }, function (confirm) {
551                            if (confirm) {
552                                $scope.addToCartServer(product);
553                            }
554                        });
555
556                        return;
557                    }
558
559                    $scope.addToCartServer(product);
560                };
561
562                $scope.prepareSafetyStockWarning = (product) => {
563                    let warningMessage = product.safetyStockWarning;
564
565                    if (!warningMessage || warningMessage.trim() === '') {
566                        warningMessage = 'Quantity is running very low ({{vm.product.QuantityAvailable}} items left)';
567                    }
568
569                    return warningMessage.replace('{{vm.product.QuantityAvailable}}', product.quantityAvailable);
570                };
571
572                $scope.addToCartServer = async (product) => {
573                    $(document).progressStart();
574
575                    // Clear join cart and switch to customer cart before adding products
576                    cartService.clearJoinCartAndSwitchToCustomerCart();
577
578                    beautysocietyCommonService.addToCartEventToGoogleTagManager({
579                        ProductDisplayName: product.displayName,
580                        ProductSKU: product.sku,
581                        Price: product.price,
582                        GoogleBrand: product.googleBrand,
583                        GoogleColor: product.googleColor || product.googleSize,
584                        Quantity: 1,
585                        BundleVirtualKit: product.bundleVirtualKit
586                    });
587
588                    const cartId = await cartService.getCartId();
589
590                    idstcApiRequests.addToCart({
591                        CartID: cartId,
592                        SKU: product.sku,
593                        Quantity: 1,
594                        BundleVirtualKit: product.bundleVirtualKit
595                    }).then(function(result) {
596                        beautysocietyCommonService.processNotifications(result?.data?.messages);
597
598                        idstcApiRequests.getViewCartInfo(true).then(function(response) {
599                            beautysocietyCommonService.processNotifications(response?.data?.Notifications);
600
601                            beautysocietyCommonService.sendCartToGoogleTagManager(response.data);
602                            beautysocietyCommonService.klaviyoAddToCart(response?.data?.OrderLines, product.sku);
603                            beautysocietyCommonService.updateCartQuantity(response?.data?.OrderLines);
604
605                            beautysocietyPosthogService.productAddedEvent(cartId, {
606                                ProductID: product?.productID,
607                                ProductSKU: product?.sku,
608                                DisplayCategoryID: beautysocietyCommonService?.selectedCategory?.DisplayCategoryID,
609                                ProductDisplayName: product?.displayName,
610                                GoogleBrand: product?.googleBrand,
611                                ProductVariation: product?.variationGroupID,
612                                Price: product?.price,
613                                QuantityAvailable: product?.quantityAvailable,
614                                CouponCode: product?.couponCode,
615                                DisplayOrder: product?.displayOrder,
616                                ProductCartUrl: product?.cartUrl,
617                                ProductImage: product?.pictureUrl
618                            });
619
620                            shoppingCartModel.viewCartInfo = response;
621
622                            $rootScope.$broadcast('cart.showCart', null);
623
624                            const customEvent = new CustomEvent('flightShowCart');
625                            window.dispatchEvent(customEvent);
626                        });
627                    }).catch(function(error) {
628                        beautysocietyToastMessageService.show(error?.data?.messages, 'error');
629                    }).finally(function() {
630                        $(document).progressFinish();
631                    });
632                };
633
634                $scope.setGetWishlistsFn = function(theSetDirFn) {
635                    $scope.getWishlists = theSetDirFn;
636                };
637
638                $scope.addToWishlist = async function(product) {
639                    if (product.QuantityAvailable === 0) {
640                        window.location = '/productdetail/' + (product.cartUrl ||product.sku);
641                        return;
642                    }
643
644                    if (!appService?.loggedIn) {
645                        $scope.loginProcess = 'wishlist';
646                        $scope.selectedWishlistProduct = product;
647                        $scope.showCustomerLogin = true;
648                        $scope.showConsultantLoginLink = false;
649                        $scope.showCreatePersonLink = true;
650                        $scope.createPersonLink = 'createperson?returnURL=' + encodeURIComponent($location.$$url + ($location.$$url.indexOf('?') >= 0 ? '&' : '?') + 'process=' + $scope.loginProcess + '&createType=petite');
651
652                        $scope.customerLoginWindowListing.center().open();
653
654                        return;
655                    }
656
657                    const cartId = await cartService.getCartId();
658
659                    $scope.addToWishlistProduct = {
660                        CartID: cartId,
661                        SKU: product.sku,
662                        ProductID: product.productID,
663                        BusinessUnitID: product.businessUnitID || product.BusinessUnitID || null,
664                        ProductImage: product.pictureUrl,
665                        DisplayName: product.displayName,
666                        CustomFields: [],
667                        BuildableComponents: [],
668                        selectedWishlistID: null
669                    };
670
671                    $scope.getWishlists();
672
673                    $scope.showCreatePersonLink = true;
674                    $scope.createPersonLink = '/productdetail/' + (product.cartUrl || product.sku) + '?returnURL=' + encodeURIComponent($location.$$url + ($location.$$url.indexOf('?') >= 0 ? '&' : '?') + 'process=' + $scope.loginProcess + '&createType=petite');
675
676                    $scope.addToWishlistComplete = false;
677
678                    $scope.addProductToWishlistWindow.center().open();
679                };
680
681                $scope.$on('customerLoginFinished', function(event, process) {
682                    if (process === 'wishlist') {
683                        $scope.customerLoginWindowListing.close();
684                        $scope.showCustomerLogin = false;
685                        
686                        $(document).progressFinish();
687                        
688                        $timeout(function() {
689                            if (typeof $scope.getWishlists === 'function') {
690                                $scope.getWishlists();
691                            }
692
693                            $scope.addToWishlist($scope.selectedWishlistProduct);
694                        }, 100);
695                    }
696                });
697
698                $scope.$on('productAddedToWishlist.Complete', function(event, sku) {
699                    const product = $scope.products.find(p => p.sku === sku);
700
701                    if (product) {
702                        product.inWishlist = true;
703                    }
704
705                    if ($scope.addProductToWishlistWindow) {
706                        $scope.addProductToWishlistWindow.close();
707                    }
708                });
709
710                $scope.gotoWishlist = function (product) {
711                    window.location = '/wishlists';
712                };
713
714                $scope.setDataLoaded = (value) => {
715                    $scope.dataLoaded = value;
716
717                    if (!$scope.$$phase && !$scope.$root.$$phase) {
718                        try {
719                            $scope.$apply();
720                        } catch (e) {
721                            // Ignore "already in progress" error
722                        }
723                    }
724                };
725
726                $scope.init = async function() {
727                    await appService.makeReady();
728
729                    loadProducts($rootScope.beautysocietyProducts || []);
730                };
731
732                $scope.init();
733            }
734        };
735    });
736})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.