PageSourceSearch

https://rezatimes.ir/shoporder/shop.js?v=2

js rezatimes.ir collected 2026-10-08 19:07:57 UTC 232,873 bytes, 9,670 lines download raw bytes

1document.addEventListener('DOMContentLoaded', function () {
2
3    document.querySelectorAll('.shop-filter-price').forEach(function (filter) {
4
5        var rangeBox = filter.querySelector('.lx_input_range');
6
7        var minRange =
8            filter.querySelector('.lx_filter_price_min_range');
9
10        var maxRange =
11            filter.querySelector('.lx_filter_price_max_range');
12
13        var minInput =
14            filter.querySelector('.lx_filter_price_min');
15
16        var maxInput =
17            filter.querySelector('.lx_filter_price_max');
18
19        if (
20            !rangeBox ||
21            !minRange ||
22            !maxRange ||
23            !minInput ||
24            !maxInput
25        ) {
26            return;
27        }
28
29        var step = parseFloat(minRange.step) || 1;
30
31
32        /*
33         * عدد را از input استخراج می‌کند
34         */
35        function getNumber(value) {
36
37            value = String(value || '');
38
39            value = value.replace(/,/g, '');
40
41            value = value.replace(/[^\d.]/g, '');
42
43            var number = parseFloat(value);
44
45            if (!isFinite(number)) {
46                return 0;
47            }
48
49            return number;
50        }
51
52
53        /*
54         * فرمت قیمت برای نمایش
55         */
56        function formatPrice(value) {
57
58            value = Math.round(value);
59
60            return value.toLocaleString('en-US');
61        }
62
63
64        /*
65         * جلوگیری از عبور min و max از یکدیگر
66         */
67        function normalize() {
68
69            var min =
70                parseFloat(minRange.value) || 0;
71
72            var max =
73                parseFloat(maxRange.value) || 0;
74
75            if (min > max) {
76
77                if (
78                    document.activeElement === minRange
79                ) {
80
81                    min = max;
82
83                    minRange.value = min;
84
85                } else {
86
87                    max = min;
88
89                    maxRange.value = max;
90                }
91            }
92
93            minInput.value =
94                formatPrice(min);
95
96            maxInput.value =
97                formatPrice(max);
98
99            updateTrack(min, max);
100        }
101
102
103        /*
104         * نوار رنگی بین دو دسته
105         */
106        function updateTrack(min, max) {
107
108            var minLimit =
109                parseFloat(minRange.min) || 0;
110
111            var maxLimit =
112                parseFloat(minRange.max) || 0;
113
114            if (maxLimit <= minLimit) {
115                return;
116            }
117
118            var start =
119                ((min - minLimit) /
120                    (maxLimit - minLimit)) * 100;
121
122            var end =
123                ((max - minLimit) /
124                    (maxLimit - minLimit)) * 100;
125
126            start =
127                Math.max(
128                    0,
129                    Math.min(100, start)
130                );
131
132            end =
133                Math.max(
134                    0,
135                    Math.min(100, end)
136                );
137
138            rangeBox.style.setProperty(
139                '--price-start',
140                start + '%'
141            );
142
143            rangeBox.style.setProperty(
144                '--price-end',
145                end + '%'
146            );
147        }
148
149
150        /*
151         * تغییر minimum
152         */
153        minRange.addEventListener('input', function () {
154
155            var min =
156                parseFloat(this.value) || 0;
157
158            var max =
159                parseFloat(maxRange.value) || 0;
160
161            if (min > max) {
162                this.value = max;
163            }
164
165            normalize();
166        });
167
168
169        /*
170         * تغییر maximum
171         */
172        maxRange.addEventListener('input', function () {
173
174            var min =
175                parseFloat(minRange.value) || 0;
176
177            var max =
178                parseFloat(this.value) || 0;
179
180            if (max < min) {
181                this.value = min;
182            }
183
184            normalize();
185        });
186
187
188        /*
189         * تغییر دستی minimum
190         */
191        minInput.addEventListener('input', function () {
192
193            var value =
194                getNumber(this.value);
195
196            var minLimit =
197                parseFloat(minRange.min) || 0;
198
199            var maxLimit =
200                parseFloat(minRange.max) || 0;
201
202            value =
203                Math.max(
204                    minLimit,
205                    Math.min(maxLimit, value)
206                );
207
208            minRange.value = value;
209
210            var max =
211                parseFloat(maxRange.value) || 0;
212
213            if (value > max) {
214                maxRange.value = value;
215            }
216
217            updateTrack(
218                parseFloat(minRange.value) || 0,
219                parseFloat(maxRange.value) || 0
220            );
221        });
222
223
224        /*
225         * تغییر دستی maximum
226         */
227        maxInput.addEventListener('input', function () {
228
229            var value =
230                getNumber(this.value);
231
232            var minLimit =
233                parseFloat(maxRange.min) || 0;
234
235            var maxLimit =
236                parseFloat(maxRange.max) || 0;
237
238            value =
239                Math.max(
240                    minLimit,
241                    Math.min(maxLimit, value)
242                );
243
244            maxRange.value = value;
245
246            var min =
247                parseFloat(minRange.value) || 0;
248
249            if (value < min) {
250                minRange.value = value;
251            }
252
253            updateTrack(
254                parseFloat(minRange.value) || 0,
255                parseFloat(maxRange.value) || 0
256            );
257        });
258
259
260        /*
261         * هنگام خروج از input، مقدار را مرتب کن
262         */
263        minInput.addEventListener('blur', function () {
264            normalize();
265        });
266
267        maxInput.addEventListener('blur', function () {
268            normalize();
269        });
270
271
272        /*
273         * قبل از ارسال فرم:
274         * کاماهای قیمت را حذف می‌کنیم
275         *
276         * نمایش:
277         * 310,000
278         *
279         * ارسال:
280         * 310000
281         */
282        var form =
283            filter.closest('.lx-shop-filter-form');
284
285        if (form) {
286
287            form.addEventListener('submit', function () {
288
289                minInput.value =
290                    getNumber(minInput.value);
291
292                maxInput.value =
293                    getNumber(maxInput.value);
294
295            });
296        }
297
298
299        /*
300         * مقدار اولیه
301         */
302        normalize();
303
304    });
305
306});
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324document.addEventListener('DOMContentLoaded', function () {
325
326    var form = document.getElementById('lxReviewSubmitForm');
327
328    if (!form) {
329        return;
330    }
331
332
333    /* =========================================================
334       Elements
335    ========================================================= */
336
337    var ratingInput = document.getElementById(
338        'lxReviewRating'
339    );
340
341    var ratingContainer = form.querySelector(
342        '.lx-rating-input'
343    );
344
345    var ratingButtons = ratingContainer
346        ? ratingContainer.querySelectorAll(
347            'button[data-rating]'
348        )
349        : [];
350
351    var ratingCaption = document.getElementById(
352        'lxRatingCaption'
353    );
354
355    var imageInput = document.getElementById(
356        'lxReviewImages'
357    );
358
359    var imagePreview = document.getElementById(
360        'lxReviewImagePreview'
361    );
362
363    var message = document.getElementById(
364        'lxReviewMessage'
365    );
366
367    var submitButton = document.getElementById(
368        'lxReviewSubmit'
369    );
370
371    var openButton = document.getElementById(
372        'lxReviewOpenForm'
373    );
374
375    var closeButton = document.getElementById(
376        'lxReviewCloseForm'
377    );
378
379    var cancelButton = document.getElementById(
380        'lxReviewCancel'
381    );
382
383    var reviewFormWrap = document.getElementById(
384        'lxReviewForm'
385    );
386
387
388    /* =========================================================
389       Rating texts
390    ========================================================= */
391
392    var ratingTexts = {
393        1: 'خیلی ضعیف',
394        2: 'ضعیف',
395        3: 'متوسط',
396        4: 'خوب',
397        5: 'عالی'
398    };
399
400
401    /* =========================================================
402       Open Form
403    ========================================================= */
404
405    if (openButton && reviewFormWrap) {
406
407        openButton.addEventListener(
408            'click',
409            function () {
410
411                reviewFormWrap.classList.add(
412                    'is-open'
413                );
414
415                try {
416
417                    reviewFormWrap.scrollIntoView({
418                        behavior: 'smooth',
419                        block: 'start'
420                    });
421
422                } catch (e) {
423
424                    reviewFormWrap.scrollIntoView();
425                }
426            }
427        );
428    }
429
430
431    /* =========================================================
432       Close Form
433    ========================================================= */
434
435    function closeReviewForm() {
436
437        if (!reviewFormWrap) {
438            return;
439        }
440
441        reviewFormWrap.classList.remove(
442            'is-open'
443        );
444    }
445
446
447    if (closeButton) {
448
449        closeButton.addEventListener(
450            'click',
451            closeReviewForm
452        );
453    }
454
455
456    if (cancelButton) {
457
458        cancelButton.addEventListener(
459            'click',
460            closeReviewForm
461        );
462    }
463
464
465    /* =========================================================
466       Rating
467    ========================================================= */
468
469    function setRating(rating) {
470
471        rating = parseInt(
472            rating,
473            10
474        );
475
476        if (
477            isNaN(rating) ||
478            rating < 0 ||
479            rating > 5
480        ) {
481            rating = 0;
482        }
483
484
485        /*
486         * Hidden input
487         */
488        if (ratingInput) {
489
490            ratingInput.value = rating;
491        }
492
493
494        /*
495         * Container state
496         */
497        if (ratingContainer) {
498
499            ratingContainer.setAttribute(
500                'data-rating',
501                rating
502            );
503        }
504
505
506        /*
507         * Stars
508         */
509        ratingButtons.forEach(
510            function (button) {
511
512                var buttonRating = parseInt(
513                    button.getAttribute(
514                        'data-rating'
515                    ),
516                    10
517                );
518
519                button.classList.remove(
520                    'is-hover'
521                );
522
523                if (
524                    buttonRating <= rating
525                ) {
526
527                    button.classList.add(
528                        'is-active'
529                    );
530
531                } else {
532
533                    button.classList.remove(
534                        'is-active'
535                    );
536                }
537            }
538        );
539
540
541        /*
542         * Caption
543         */
544        if (ratingCaption) {
545
546            if (
547                rating > 0 &&
548                ratingTexts[rating]
549            ) {
550
551                ratingCaption.textContent =
552                    ratingTexts[rating];
553
554                ratingCaption.classList.add(
555                    'is-selected'
556                );
557
558            } else {
559
560                ratingCaption.textContent =
561                    'امتیاز خود را انتخاب کنید';
562
563                ratingCaption.classList.remove(
564                    'is-selected'
565                );
566            }
567        }
568    }
569
570
571    /*
572     * Hover preview
573     */
574    function showRatingHover(rating) {
575
576        rating = parseInt(
577            rating,
578            10
579        );
580
581        if (
582            isNaN(rating) ||
583            rating < 1 ||
584            rating > 5
585        ) {
586            return;
587        }
588
589
590        if (ratingContainer) {
591
592            ratingContainer.classList.add(
593                'is-hovering'
594            );
595        }
596
597
598        ratingButtons.forEach(
599            function (button) {
600
601                var buttonRating = parseInt(
602                    button.getAttribute(
603                        'data-rating'
604                    ),
605                    10
606                );
607
608                button.classList.remove(
609                    'is-hover'
610                );
611
612
613                if (
614                    buttonRating <= rating
615                ) {
616
617                    button.classList.add(
618                        'is-hover'
619                    );
620                }
621            }
622        );
623
624
625        /*
626         * Hover caption
627         */
628        if (
629            ratingCaption &&
630            ratingTexts[rating]
631        ) {
632
633            ratingCaption.textContent =
634                ratingTexts[rating];
635        }
636    }
637
638
639    /*
640     * Remove hover and restore selected rating
641     */
642    function restoreSelectedRating() {
643
644        if (ratingContainer) {
645
646            ratingContainer.classList.remove(
647                'is-hovering'
648            );
649        }
650
651
652        ratingButtons.forEach(
653            function (button) {
654
655                button.classList.remove(
656                    'is-hover'
657                );
658            }
659        );
660
661
662        var selectedRating = ratingInput
663            ? parseInt(
664                ratingInput.value,
665                10
666            )
667            : 0;
668
669
670        if (isNaN(selectedRating)) {
671            selectedRating = 0;
672        }
673
674
675        /*
676         * Restore stars
677         */
678        ratingButtons.forEach(
679            function (button) {
680
681                var buttonRating = parseInt(
682                    button.getAttribute(
683                        'data-rating'
684                    ),
685                    10
686                );
687
688
689                if (
690                    buttonRating <= selectedRating
691                ) {
692
693                    button.classList.add(
694                        'is-active'
695                    );
696
697                } else {
698
699                    button.classList.remove(
700                        'is-active'
701                    );
702                }
703            }
704        );
705
706
707        /*
708         * Restore caption
709         */
710        if (ratingCaption) {
711
712            if (
713                selectedRating > 0 &&
714                ratingTexts[selectedRating]
715            ) {
716
717                ratingCaption.textContent =
718                    ratingTexts[selectedRating];
719
720                ratingCaption.classList.add(
721                    'is-selected'
722                );
723
724            } else {
725
726                ratingCaption.textContent =
727                    'امتیاز خود را انتخاب کنید';
728
729                ratingCaption.classList.remove(
730                    'is-selected'
731                );
732            }
733        }
734    }
735
736
737    /*
738     * Attach rating events
739     */
740    if (
741        ratingContainer &&
742        ratingButtons.length
743    ) {
744
745        ratingButtons.forEach(
746            function (button) {
747
748                /*
749                 * Mouse enters star
750                 */
751                button.addEventListener(
752                    'mouseenter',
753                    function () {
754
755                        var hoverRating =
756                            parseInt(
757                                button.getAttribute(
758                                    'data-rating'
759                                ),
760                                10
761                            );
762
763                        showRatingHover(
764                            hoverRating
765                        );
766                    }
767                );
768
769
770                /*
771                 * Click star
772                 */
773                button.addEventListener(
774                    'click',
775                    function () {
776
777                        var rating =
778                            parseInt(
779                                button.getAttribute(
780                                    'data-rating'
781                                ),
782                                10
783                            );
784
785                        if (
786                            isNaN(rating) ||
787                            rating < 1 ||
788                            rating > 5
789                        ) {
790                            return;
791                        }
792
793                        setRating(
794                            rating
795                        );
796                    }
797                );
798            }
799        );
800
801
802        /*
803         * Mouse leaves rating area
804         */
805        ratingContainer.addEventListener(
806            'mouseleave',
807            function () {
808
809                restoreSelectedRating();
810            }
811        );
812    }
813
814
815    /* =========================================================
816       Image Preview
817    ========================================================= */
818
819    if (
820        imageInput &&
821        imagePreview
822    ) {
823
824        imageInput.addEventListener(
825            'change',
826            function () {
827
828                imagePreview.innerHTML = '';
829
830
831                var files =
832                    imageInput.files;
833
834
835                if (
836                    !files ||
837                    !files.length
838                ) {
839
840                    return;
841                }
842
843
844                /*
845                 * Maximum 5 images
846                 */
847                if (files.length > 5) {
848
849                    showMessage(
850                        'error',
851                        'حداکثر ۵ تصویر می‌توانید انتخاب کنید.'
852                    );
853
854                    imageInput.value = '';
855
856                    return;
857                }
858
859
860                /*
861                 * Check files
862                 */
863                for (
864                    var i = 0;
865                    i < files.length;
866                    i++
867                ) {
868
869                    var file =
870                        files[i];
871
872
873                    /*
874                     * Maximum 5MB
875                     */
876                    if (
877                        file.size >
878                        5 * 1024 * 1024
879                    ) {
880
881                        showMessage(
882                            'error',
883                            'حجم هر تصویر حداکثر ۵ مگابایت است.'
884                        );
885
886                        imageInput.value = '';
887
888                        imagePreview.innerHTML =
889                            '';
890
891                        return;
892                    }
893
894
895                    /*
896                     * Check image type
897                     */
898                    if (
899                        !file.type ||
900                        file.type.indexOf(
901                            'image/'
902                        ) !== 0
903                    ) {
904
905                        showMessage(
906                            'error',
907                            'فقط فایل‌های تصویری قابل انتخاب هستند.'
908                        );
909
910                        imageInput.value = '';
911
912                        imagePreview.innerHTML =
913                            '';
914
915                        return;
916                    }
917
918
919                    /*
920                     * Preview
921                     */
922                    var reader =
923                        new FileReader();
924
925
926                    reader.onload =
927                        (function (file) {
928
929                            return function (event) {
930
931                                var item =
932                                    document.createElement(
933                                        'div'
934                                    );
935
936                                item.className =
937                                    'lx-review-image-preview-item';
938
939
940                                var img =
941                                    document.createElement(
942                                        'img'
943                                    );
944
945                                img.src =
946                                    event.target.result;
947
948                                img.alt = '';
949
950
951                                item.appendChild(
952                                    img
953                                );
954
955
956                                imagePreview.appendChild(
957                                    item
958                                );
959                            };
960
961                        })(file);
962
963
964                    reader.readAsDataURL(
965                        file
966                    );
967                }
968            }
969        );
970    }
971
972
973    /* =========================================================
974       Form Submit
975    ========================================================= */
976
977    form.addEventListener(
978        'submit',
979        function (event) {
980
981            event.preventDefault();
982
983
984            hideMessage();
985
986
987            /*
988             * Rating
989             */
990            var rating =
991                ratingInput
992                    ? parseInt(
993                        ratingInput.value,
994                        10
995                    )
996                    : 0;
997
998
999            if (
1000                !rating ||
1001                rating < 1 ||
1002                rating > 5
1003            ) {
1004
1005                showMessage(
1006                    'error',
1007                    'لطفاً امتیاز خود را انتخاب کنید.'
1008                );
1009
1010                return;
1011            }
1012
1013
1014            /*
1015             * Body
1016             */
1017            var bodyInput =
1018                form.querySelector(
1019                    'textarea[name="body"]'
1020                );
1021
1022
1023            if (
1024                !bodyInput ||
1025                !bodyInput.value.trim()
1026            ) {
1027
1028                showMessage(
1029                    'error',
1030                    'لطفاً متن نظر خود را وارد کنید.'
1031                );
1032
1033
1034                if (bodyInput) {
1035
1036                    bodyInput.focus();
1037                }
1038
1039
1040                return;
1041            }
1042
1043
1044            /*
1045             * Disable submit
1046             */
1047            if (submitButton) {
1048
1049                submitButton.disabled =
1050                    true;
1051
1052                submitButton.classList.add(
1053                    'is-loading'
1054                );
1055            }
1056
1057
1058            /*
1059             * FormData
1060             */
1061            var formData =
1062                new FormData(form);
1063
1064
1065            /*
1066             * Make sure action exists
1067             */
1068            if (
1069                !formData.get('action')
1070            ) {
1071
1072                formData.append(
1073                    'action',
1074                    'review_submit'
1075                );
1076            }
1077
1078
1079            /*
1080             * Make sure product_id exists
1081             *
1082             * در حالت عادی از hidden input فرم
1083             * ارسال می‌شود.
1084             */
1085            if (
1086                !formData.get('product_id')
1087            ) {
1088
1089                var productIdInput =
1090                    form.querySelector(
1091                        'input[name="product_id"]'
1092                    );
1093
1094
1095                if (
1096                    productIdInput &&
1097                    productIdInput.value
1098                ) {
1099
1100                    formData.set(
1101                        'product_id',
1102                        productIdInput.value
1103                    );
1104                }
1105            }
1106
1107
1108            /*
1109             * AJAX
1110             */
1111            fetch(
1112                '/coreshop/ajax.php',
1113                {
1114                    method: 'POST',
1115                    body: formData,
1116                    credentials: 'same-origin'
1117                }
1118            )
1119                .then(
1120                    function (response) {
1121
1122                        if (!response.ok) {
1123
1124                            throw new Error(
1125                                'HTTP ' +
1126                                response.status
1127                            );
1128                        }
1129
1130
1131                        return response.json();
1132                    }
1133                )
1134                .then(
1135                    function (result) {
1136
1137                        if (
1138                            result &&
1139                            result.success
1140                        ) {
1141
1142                            showMessage(
1143                                'success',
1144                                result.message ||
1145                                'نظر شما با موفقیت ثبت شد.'
1146                            );
1147
1148
1149                            /*
1150                             * Reset form
1151                             *
1152                             * توجه:
1153                             * hidden product_id هم reset می‌شود
1154                             * و ممکن است خالی شود.
1155                             *
1156                             * بنابراین بعد از reset دوباره
1157                             * product_id را از مقدار قبلی برمی‌گردانیم.
1158                             */
1159                            var productIdValue =
1160                                form.querySelector(
1161                                    'input[name="product_id"]'
1162                                );
1163
1164
1165                            var savedProductId =
1166                                productIdValue
1167                                    ? productIdValue.value
1168                                    : '';
1169
1170
1171                            form.reset();
1172
1173
1174                            /*
1175                             * Restore product ID
1176                             */
1177                            if (
1178                                productIdValue &&
1179                                savedProductId
1180                            ) {
1181
1182                                productIdValue.value =
1183                                    savedProductId;
1184                            }
1185
1186
1187                            /*
1188                             * Reset rating
1189                             */
1190                            setRating(0);
1191
1192
1193                            /*
1194                             * Clear hover state
1195                             */
1196                            if (ratingContainer) {
1197
1198                                ratingContainer.classList.remove(
1199                                    'is-hovering'
1200                                );
1201                            }
1202
1203
1204                            ratingButtons.forEach(
1205                                function (button) {
1206
1207                                    button.classList.remove(
1208                                        'is-active'
1209                                    );
1210
1211                                    button.classList.remove(
1212                                        'is-hover'
1213                                    );
1214                                }
1215                            );
1216
1217
1218                            /*
1219                             * Clear image preview
1220                             */
1221                            if (imagePreview) {
1222
1223                                imagePreview.innerHTML =
1224                                    '';
1225                            }
1226
1227
1228                            /*
1229                             * Store review ID
1230                             */
1231                            if (
1232                                result.review_id
1233                            ) {
1234
1235                                form.setAttribute(
1236                                    'data-review-id',
1237                                    result.review_id
1238                                );
1239                            }
1240
1241                        } else {
1242
1243                            showMessage(
1244                                'error',
1245                                (
1246                                    result &&
1247                                    result.message
1248                                ) ||
1249                                'ثبت نظر انجام نشد.'
1250                            );
1251                        }
1252                    }
1253                )
1254                .catch(
1255                    function (error) {
1256
1257                        console.error(
1258                            'Review submit error:',
1259                            error
1260                        );
1261
1262
1263                        showMessage(
1264                            'error',
1265                            'خطایی در ارتباط با سرور رخ داد. لطفاً دوباره تلاش کنید.'
1266                        );
1267                    }
1268                )
1269                .finally(
1270                    function () {
1271
1272                        if (submitButton) {
1273
1274                            submitButton.classList.remove(
1275                                'is-loading'
1276                            );
1277
1278                            submitButton.disabled =
1279                                false;
1280                        }
1281                    }
1282                );
1283        }
1284    );
1285
1286
1287    /* =========================================================
1288       Show Message
1289    ========================================================= */
1290
1291    function showMessage(
1292        type,
1293        text
1294    ) {
1295
1296        if (!message) {
1297            return;
1298        }
1299
1300
1301        message.className =
1302            'lx-review-message';
1303
1304
1305        if (
1306            type === 'success'
1307        ) {
1308
1309            message.classList.add(
1310                'is-success'
1311            );
1312
1313        } else {
1314
1315            message.classList.add(
1316                'is-error'
1317            );
1318        }
1319
1320
1321        message.textContent =
1322            text;
1323
1324
1325        message.style.display =
1326            'block';
1327
1328
1329        try {
1330
1331            message.scrollIntoView({
1332                behavior: 'smooth',
1333                block: 'nearest'
1334            });
1335
1336        } catch (e) {
1337
1338            /* Ignore */
1339        }
1340    }
1341
1342
1343    /* =========================================================
1344       Hide Message
1345    ========================================================= */
1346
1347    function hideMessage() {
1348
1349        if (!message) {
1350            return;
1351        }
1352
1353
1354        message.className =
1355            'lx-review-message';
1356
1357
1358        message.textContent =
1359            '';
1360
1361
1362        message.style.display =
1363            'none';
1364    }
1365
1366});
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389(function ($) {
1390
1391    'use strict';
1392
1393
1394    /*
1395     * ============================================================
1396     * LoxShop Product Gallery
1397     * ============================================================
1398     */
1399
1400    var mainSwiper = null;
1401
1402    var thumbsSwiper = null;
1403
1404    var pendingVariantImage = null;
1405
1406    /*
1407     * ============================================================
1408     * Normalize Image URL
1409     * ============================================================
1410     */
1411
1412    function normalizeImageUrl(url) {
1413
1414        if (!url) {
1415            return '';
1416        }
1417
1418        url = String(url).trim();
1419
1420        try {
1421
1422            var parsed = new URL(
1423                url,
1424                window.location.href
1425            );
1426
1427            /*
1428             * برای مقایسه، query string را حذف می‌کنیم.
1429             *
1430             * مثال:
1431             *
1432             * image.jpg
1433             *
1434             * image.jpg?w=800
1435             *
1436             * هر دو یکی در نظر گرفته می‌شوند.
1437             */
1438
1439            parsed.search = '';
1440
1441            return parsed.href;
1442
1443        } catch (e) {
1444
1445            return url.split('?')[0];
1446
1447        }
1448
1449    }
1450
1451
1452    /*
1453     * ============================================================
1454     * Find Main Slide
1455     * ============================================================
1456     */
1457
1458    function findSlideIndex(imageUrl) {
1459
1460        var target =
1461            normalizeImageUrl(imageUrl);
1462
1463        var index = -1;
1464
1465
1466        $('.lx-gallery-main .swiper-slide').each(
1467            function (i) {
1468
1469                var slideUrl =
1470                    $(this).attr(
1471                        'data-product-image'
1472                    );
1473
1474
1475                if (
1476                    normalizeImageUrl(slideUrl) ===
1477                    target
1478                ) {
1479
1480                    index = i;
1481
1482                    return false;
1483
1484                }
1485
1486            }
1487        );
1488
1489
1490        return index;
1491
1492    }
1493
1494
1495    /*
1496     * ============================================================
1497     * Create Main Slide
1498     * ============================================================
1499     */
1500
1501    function createMainSlide(imageUrl) {
1502
1503        var safeUrl = escapeHtmlAttribute(imageUrl);
1504
1505        return (
1506            '<div class="swiper-slide"' +
1507            ' data-product-image="' + safeUrl + '">' +
1508
1509            '<a class="lx-gallery-pswp-link"' +
1510            ' href="' + safeUrl + '"' +
1511            ' data-pswp-src="' + safeUrl + '">' +
1512
1513            '<div class="swiper-zoom-container">' +
1514
1515            '<img' +
1516            ' src="' + safeUrl + '"' +
1517            ' alt=""' +
1518            ' class="lx-gallery-main-image">' +
1519
1520            '</div>' +
1521
1522            '</a>' +
1523
1524            '</div>'
1525        );
1526    }
1527
1528    /*
1529     * ============================================================
1530     * Create Thumbnail
1531     * ============================================================
1532     */
1533
1534    function createThumbnail(imageUrl) {
1535
1536        return (
1537            '<div class="swiper-slide">' +
1538
1539            '<button' +
1540            ' type="button"' +
1541            ' class="lx-gallery-thumb"' +
1542            '>' +
1543
1544            '<img' +
1545            ' src="' +
1546            escapeHtmlAttribute(imageUrl) +
1547            '?w=180"' +
1548            ' alt=""' +
1549            '>' +
1550
1551            '</button>' +
1552
1553            '</div>'
1554        );
1555
1556    }
1557
1558
1559    /*
1560     * ============================================================
1561     * Create PhotoSwipe Item
1562     * ============================================================
1563     */
1564
1565    function createPhotoSwipeItem(imageUrl) {
1566
1567        var safeUrl =
1568            escapeHtmlAttribute(imageUrl);
1569
1570        return (
1571            '<a' +
1572            ' href="' + safeUrl + '"' +
1573            ' data-pswp-src="' + safeUrl + '"' +
1574            '>' +
1575
1576            '<img' +
1577            ' src="' + safeUrl + '?w=150"' +
1578            ' alt=""' +
1579            '>' +
1580
1581            '</a>'
1582        );
1583    }
1584
1585
1586    /*
1587     * ============================================================
1588     * Escape HTML Attribute
1589     * ============================================================
1590     */
1591
1592    function escapeHtmlAttribute(value) {
1593
1594        return String(value)
1595            .replace(/&/g, '&amp;')
1596            .replace(/"/g, '&quot;')
1597            .replace(/</g, '&lt;')
1598            .replace(/>/g, '&gt;')
1599            .replace(/'/g, '&#039;');
1600
1601    }
1602
1603
1604    /*
1605     * ============================================================
1606     * Add Image To Gallery
1607     * ============================================================
1608     */
1609
1610    function addImageToGallery(imageUrl) {
1611
1612        if (!mainSwiper) {
1613            return -1;
1614        }
1615
1616
1617        /*
1618         * اول بررسی می‌کنیم که تصویر وجود دارد یا نه.
1619         */
1620
1621        var existingIndex =
1622            findSlideIndex(imageUrl);
1623
1624
1625        if (existingIndex >= 0) {
1626
1627            return existingIndex;
1628
1629        }
1630
1631
1632        /*
1633         * ========================================================
1634         * MAIN SLIDE
1635         * ========================================================
1636         */
1637
1638        mainSwiper.appendSlide(
1639            createMainSlide(imageUrl)
1640        );
1641
1642
1643        /*
1644         * ========================================================
1645         * THUMBNAIL
1646         * ========================================================
1647         */
1648
1649        if (thumbsSwiper) {
1650
1651            thumbsSwiper.appendSlide(
1652                createThumbnail(imageUrl)
1653            );
1654
1655        }
1656
1657
1658
1659
1660        /*
1661         * ========================================================
1662         * UPDATE SWIPER
1663         * ========================================================
1664         */
1665
1666        mainSwiper.update();
1667
1668
1669        if (thumbsSwiper) {
1670
1671            thumbsSwiper.update();
1672
1673        }
1674
1675
1676        /*
1677         * index تصویر جدید
1678         */
1679
1680        return mainSwiper.slides.length - 1;
1681
1682    }
1683
1684
1685    /*
1686     * ============================================================
1687     * Show Image
1688     * ============================================================
1689     */
1690
1691    function showImage(imageUrl) {
1692
1693        if (!imageUrl) {
1694            return;
1695        }
1696
1697        if (!mainSwiper) {
1698
1699            pendingVariantImage = imageUrl;
1700
1701            return;
1702        }
1703
1704        /*
1705         * تصویر قبلاً در گالری وجود دارد؟
1706         */
1707
1708        var index =
1709            findSlideIndex(imageUrl);
1710
1711
1712        /*
1713         * اگر وجود ندارد، اضافه‌اش می‌کنیم.
1714         */
1715
1716        if (index < 0) {
1717
1718            index =
1719                addImageToGallery(imageUrl);
1720
1721        }
1722
1723
1724        if (index < 0) {
1725            return;
1726        }
1727
1728
1729        /*
1730         * رفتن به تصویر Variation
1731         */
1732
1733        mainSwiper.slideTo(
1734            index,
1735            500
1736        );
1737
1738
1739        /*
1740         * اطمینان از حرکت thumbnail
1741         */
1742
1743        if (thumbsSwiper) {
1744
1745            thumbsSwiper.slideTo(
1746                index,
1747                500
1748            );
1749
1750        }
1751
1752
1753        /*
1754         * Counter
1755         */
1756
1757        updateCounter();
1758
1759    }
1760
1761
1762    /*
1763     * ============================================================
1764     * Counter
1765     * ============================================================
1766     */
1767
1768    function updateCounter() {
1769
1770        if (!mainSwiper) {
1771            return;
1772        }
1773
1774
1775        var current =
1776            mainSwiper.activeIndex + 1;
1777
1778
1779        var total =
1780            mainSwiper.slides.length;
1781
1782
1783        $('.lx-gallery-current')
1784            .text(current);
1785
1786
1787        $('.lx-gallery-total')
1788            .text(total);
1789
1790    }
1791
1792
1793    /*
1794     * ============================================================
1795     * Init
1796     * ============================================================
1797     */
1798
1799
1800    /*
1801     * ============================================================
1802     * PhotoSwipe Lightbox
1803     * ============================================================
1804     */
1805
1806    function initPhotoSwipe() {
1807
1808        if (
1809            typeof PhotoSwipeLightbox === 'undefined' ||
1810            typeof PhotoSwipe === 'undefined'
1811        ) {
1812            return;
1813        }
1814
1815
1816        var lightbox = new PhotoSwipeLightbox({
1817
1818            /*
1819             * گالری‌های PhotoSwipe
1820             *
1821             * هر کدام از این containerها
1822             * یک گالری مستقل محسوب می‌شوند.
1823             */
1824            gallery:
1825                '.lx-gallery-main, .lx-review-images',
1826
1827            /*
1828             * لینک‌های قابل کلیک داخل گالری
1829             */
1830            children:
1831                '.lx-gallery-pswp-link, .lx-review-photo',
1832
1833            pswpModule: PhotoSwipe,
1834
1835            bgOpacity: 0.96,
1836
1837            showHideAnimationType: 'zoom',
1838
1839            wheelToZoom: true,
1840
1841            pinchToClose: true,
1842
1843            closeOnVerticalDrag: true,
1844
1845            paddingFn: function () {
1846
1847                return {
1848
1849                    top: 30,
1850
1851                    bottom: 30,
1852
1853                    left: 30,
1854
1855                    right: 30
1856                };
1857            }
1858        });
1859
1860
1861        /*
1862         * ============================================================
1863         * دریافت ابعاد واقعی تصویر
1864         * ============================================================
1865         *
1866         * لازم نیست width و height را در دیتابیس ذخیره کنیم.
1867         *
1868         * PhotoSwipe قبل از نمایش، ابعاد تصویر موجود
1869         * در همان <img> را دریافت می‌کند.
1870         */
1871        lightbox.addFilter(
1872            'itemData',
1873            function (itemData, index) {
1874
1875                /*
1876                 * اگر ابعاد از قبل وجود دارد،
1877                 * نیازی به ادامه نیست.
1878                 */
1879                if (
1880                    itemData.width &&
1881                    itemData.height
1882                ) {
1883
1884                    return itemData;
1885                }
1886
1887
1888                /*
1889                 * URL تصویر
1890                 */
1891                var src =
1892                    itemData.src ||
1893                    (
1894                        itemData.element
1895                            ? itemData.element.getAttribute('href')
1896                            : ''
1897                    );
1898
1899
1900                if (!src) {
1901
1902                    return itemData;
1903                }
1904
1905
1906                /*
1907                 * اگر قبلاً ابعاد این تصویر را
1908                 * روی element ذخیره کرده‌ایم،
1909                 * دوباره محاسبه نکن.
1910                 */
1911                var cached =
1912                    itemData.element &&
1913                    itemData.element.dataset.pswpWidth;
1914
1915
1916                if (cached) {
1917
1918                    itemData.width =
1919                        parseInt(
1920                            itemData.element.dataset.pswpWidth,
1921                            10
1922                        );
1923
1924                    itemData.height =
1925                        parseInt(
1926                            itemData.element.dataset.pswpHeight,
1927                            10
1928                        );
1929
1930                    return itemData;
1931                }
1932
1933
1934                /*
1935                 * تصویر داخل لینک
1936                 *
1937                 * هم برای تصاویر محصول کار می‌کند
1938                 * هم برای تصاویر کامنت.
1939                 */
1940                var img =
1941                    itemData.element
1942                        ? itemData.element.querySelector('img')
1943                        : null;
1944
1945
1946                if (
1947                    img &&
1948                    img.naturalWidth > 0 &&
1949                    img.naturalHeight > 0
1950                ) {
1951
1952                    itemData.width =
1953                        img.naturalWidth;
1954
1955                    itemData.height =
1956                        img.naturalHeight;
1957
1958
1959                    /*
1960                     * Cache
1961                     */
1962                    if (itemData.element) {
1963
1964                        itemData.element.dataset.pswpWidth =
1965                            img.naturalWidth;
1966
1967                        itemData.element.dataset.pswpHeight =
1968                            img.naturalHeight;
1969                    }
1970                }
1971
1972
1973                return itemData;
1974            }
1975        );
1976
1977
1978        /*
1979         * شروع PhotoSwipe
1980         */
1981        lightbox.init();
1982
1983
1984        /*
1985         * دسترسی عمومی
1986         */
1987        window.lxProductPhotoSwipe =
1988            lightbox;
1989    }
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002    function init() {
2003
2004        /*
2005         * اگر گالری وجود ندارد، کاری نکن.
2006         */
2007
2008        if (
2009            !document.querySelector(
2010                '.lx-gallery-main'
2011            )
2012        ) {
2013
2014            return;
2015
2016        }
2017
2018
2019        /*
2020         * ========================================================
2021         * THUMBNAILS SWIPER
2022         * ========================================================
2023         */
2024
2025        thumbsSwiper = new Swiper(
2026            '.lx-gallery-thumbs',
2027            {
2028
2029                slidesPerView: 5,
2030
2031                spaceBetween: 10,
2032
2033                watchSlidesProgress: true,
2034
2035                navigation: {
2036
2037                    nextEl:
2038                        '.lx-thumb-next',
2039
2040                    prevEl:
2041                        '.lx-thumb-prev'
2042
2043                },
2044
2045                breakpoints: {
2046
2047                    0: {
2048
2049                        slidesPerView: 4
2050
2051                    },
2052
2053                    576: {
2054
2055                        slidesPerView: 5
2056
2057                    },
2058
2059                    768: {
2060
2061                        slidesPerView: 6
2062
2063                    }
2064
2065                }
2066
2067            }
2068        );
2069
2070
2071        /*
2072         * ========================================================
2073         * MAIN SWIPER
2074         * ========================================================
2075         */
2076
2077        mainSwiper = new Swiper(
2078            '.lx-gallery-main',
2079            {
2080
2081                slidesPerView: 1,
2082
2083                spaceBetween: 0,
2084
2085                speed: 500,
2086
2087                direction: 'horizontal',
2088
2089                navigation: {
2090
2091                    nextEl:
2092                        '.lx-gallery-button-next',
2093
2094                    prevEl:
2095                        '.lx-gallery-button-prev'
2096
2097                },
2098
2099                zoom: {
2100
2101                    maxRatio: 3
2102
2103                },
2104
2105                thumbs: {
2106
2107                    swiper:
2108                        thumbsSwiper
2109
2110                },
2111
2112                on: {
2113
2114                    init: function () {
2115
2116                        updateCounter();
2117
2118                    },
2119
2120                    slideChange: function () {
2121
2122                        updateCounter();
2123
2124                    }
2125
2126                }
2127
2128            }
2129        );
2130
2131        initPhotoSwipe();
2132
2133        /*
2134         * ========================================================
2135         * PUBLIC API
2136         * ========================================================
2137         */
2138
2139        window.lxProductGallery = {
2140
2141            /*
2142             * نمایش تصویر
2143             */
2144
2145            showImage:
2146                showImage,
2147
2148
2149            /*
2150             * اضافه کردن تصویر
2151             */
2152
2153            addImage:
2154                addImageToGallery,
2155
2156
2157            /*
2158             * گرفتن Main Swiper
2159             */
2160
2161            getMainSwiper:
2162                function () {
2163
2164                    return mainSwiper;
2165
2166                },
2167
2168
2169            /*
2170             * گرفتن Thumbnail Swiper
2171             */
2172
2173            getThumbsSwiper:
2174                function () {
2175
2176                    return thumbsSwiper;
2177
2178                }
2179
2180        };
2181
2182
2183
2184        if (pendingVariantImage) {
2185
2186            var imageUrl =
2187                pendingVariantImage;
2188
2189            pendingVariantImage = null;
2190
2191            showImage(imageUrl);
2192
2193        }
2194
2195
2196    }
2197
2198
2199    /*
2200     * ============================================================
2201     * Start
2202     * ============================================================
2203     */
2204
2205    $(function () {
2206
2207        init();
2208
2209    });
2210
2211
2212})(jQuery);
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245(function (window, document) {
2246
2247    'use strict';
2248
2249    /*
2250    |--------------------------------------------------------------------------
2251    |--------------------------------------------------------------------------
2252    | LOXBLOG SHOP CART
2253    |--------------------------------------------------------------------------
2254    |--------------------------------------------------------------------------
2255    */
2256
2257    const ShopCart = {
2258
2259        endpoint: '/shoporder/checkorder.php',
2260
2261
2262        /*
2263        |--------------------------------------------------------------------------
2264        | AJAX REQUEST
2265        |--------------------------------------------------------------------------
2266        */
2267
2268        request: async function (data) {
2269
2270            let response;
2271
2272            try {
2273
2274                response = await fetch(
2275                    this.endpoint,
2276                    {
2277                        method: 'POST',
2278                        headers: {
2279                            'Content-Type': 'application/json',
2280                            'X-Requested-With': 'XMLHttpRequest'
2281                        },
2282                        credentials: 'same-origin',
2283                        body: JSON.stringify(data)
2284                    }
2285                );
2286
2287            } catch (error) {
2288
2289                throw {
2290                    success: false,
2291                    error: 'network_error',
2292                    message: 'ارتباط با سرور برقرار نشد.'
2293                };
2294            }
2295
2296
2297            let result;
2298
2299            try {
2300
2301                result = await response.json();
2302
2303            } catch (error) {
2304
2305                throw {
2306                    success: false,
2307                    error: 'invalid_response',
2308                    message: 'پاسخ سرور معتبر نیست.'
2309                };
2310            }
2311
2312
2313            if (
2314                !response.ok ||
2315                !result ||
2316                result.success !== true
2317            ) {
2318
2319                throw result || {
2320                    success: false,
2321                    error: 'server_error',
2322                    message: 'خطایی در سرور رخ داده است.'
2323                };
2324            }
2325
2326            return result;
2327        },
2328
2329
2330        /*
2331        |--------------------------------------------------------------------------
2332        | GET CHECKOUT SESSION
2333        |--------------------------------------------------------------------------
2334        |
2335        | اطلاعات Checkout Session از خود DOM خوانده می‌شود.
2336        |
2337        */
2338
2339        getCheckoutSession: function () {
2340
2341            const box =
2342                document.getElementById(
2343                    'LoxShopCoupon'
2344                );
2345
2346            if (!box) {
2347                return null;
2348            }
2349
2350
2351            const checkoutSessionId =
2352                parseInt(
2353                    box.getAttribute(
2354                        'data-checkout-session-id'
2355                    ) || 0,
2356                    10
2357                );
2358
2359
2360            const checkoutSessionToken =
2361                (
2362                    box.getAttribute(
2363                        'data-checkout-session-token'
2364                    ) || ''
2365                ).trim();
2366
2367
2368            if (
2369                checkoutSessionId <= 0 ||
2370                checkoutSessionToken === ''
2371            ) {
2372                return null;
2373            }
2374
2375
2376            return {
2377                id: checkoutSessionId,
2378                token: checkoutSessionToken
2379            };
2380        },
2381
2382
2383        /*
2384        |--------------------------------------------------------------------------
2385        | GET COUPON CODE
2386        |--------------------------------------------------------------------------
2387        */
2388
2389        getCouponCode: function () {
2390
2391            const input =
2392                document.getElementById(
2393                    'shop_coupon_code'
2394                );
2395
2396            if (!input) {
2397                return '';
2398            }
2399
2400            return (
2401                input.value || ''
2402            ).trim();
2403        },
2404
2405
2406        /*
2407        |--------------------------------------------------------------------------
2408        | SET LOADING
2409        |--------------------------------------------------------------------------
2410        */
2411
2412        setCouponLoading: function (
2413            loading
2414        ) {
2415
2416            const applyButton =
2417                document.getElementById(
2418                    'shop_coupon_apply'
2419                );
2420
2421            const removeButton =
2422                document.getElementById(
2423                    'shop_coupon_remove'
2424                );
2425
2426
2427            if (applyButton) {
2428
2429                if (loading) {
2430
2431                    if (
2432                        applyButton.dataset.couponLoading === '1'
2433                    ) {
2434                        return false;
2435                    }
2436
2437                    applyButton.dataset.couponLoading = '1';
2438                    applyButton.disabled = true;
2439
2440                } else {
2441
2442                    applyButton.dataset.couponLoading = '0';
2443                    applyButton.disabled = false;
2444                }
2445            }
2446
2447
2448            if (removeButton) {
2449
2450                removeButton.disabled = loading;
2451            }
2452
2453            return true;
2454        },
2455
2456
2457        /*
2458        |--------------------------------------------------------------------------
2459        | COUPON MESSAGE
2460        |--------------------------------------------------------------------------
2461        */
2462
2463        showCouponMessage: function (
2464            message,
2465            type
2466        ) {
2467
2468            const element =
2469                document.getElementById(
2470                    'shop_coupon_message'
2471                );
2472
2473            if (!element) {
2474                return;
2475            }
2476
2477
2478            element.className =
2479                'mT_10 ' +
2480                (
2481                    type === 'success'
2482                        ? 'msuccess'
2483                        : 'merror'
2484                );
2485
2486
2487            element.textContent =
2488                message || '';
2489
2490
2491            element.hidden =
2492                !message;
2493        },
2494
2495
2496        /*
2497        |--------------------------------------------------------------------------
2498        | CLEAR COUPON MESSAGE
2499        |--------------------------------------------------------------------------
2500        */
2501
2502        clearCouponMessage: function () {
2503
2504            const element =
2505                document.getElementById(
2506                    'shop_coupon_message'
2507                );
2508
2509            if (!element) {
2510                return;
2511            }
2512
2513            element.textContent = '';
2514            element.hidden = true;
2515        },
2516
2517
2518        /*
2519        |--------------------------------------------------------------------------
2520        | UPDATE CHECKOUT TOTALS
2521        |--------------------------------------------------------------------------
2522        |
2523        | نتیجه coupon_apply / coupon_remove
2524        | مبلغ نهایی Checkout را برمی‌گرداند.
2525        |
2526        */
2527
2528        handleCouponCheckout: function (
2529            result
2530        ) {
2531
2532            if (!result) {
2533                return;
2534            }
2535
2536
2537            /*
2538            |--------------------------------------------------------------------------
2539            | FINAL DISCOUNT
2540            |--------------------------------------------------------------------------
2541            */
2542
2543            document
2544                .querySelectorAll(
2545                    '[data-checkout-discount]'
2546                )
2547                .forEach(function (element) {
2548
2549                    element.textContent =
2550                        this.formatMoney(
2551                            result.discount_amount || 0
2552                        );
2553
2554                }, this);
2555
2556
2557            /*
2558            |--------------------------------------------------------------------------
2559            | CART DISCOUNT
2560            |--------------------------------------------------------------------------
2561            */
2562
2563            document
2564                .querySelectorAll(
2565                    '[data-checkout-cart-discount]'
2566                )
2567                .forEach(function (element) {
2568
2569                    element.textContent =
2570                        this.formatMoney(
2571                            result.cart_discount || 0
2572                        );
2573
2574                }, this);
2575
2576
2577            /*
2578            |--------------------------------------------------------------------------
2579            | COUPON DISCOUNT
2580            |--------------------------------------------------------------------------
2581            */
2582
2583            document
2584                .querySelectorAll(
2585                    '[data-checkout-coupon-discount]'
2586                )
2587                .forEach(function (element) {
2588
2589                    element.textContent =
2590                        this.formatMoney(
2591                            result.coupon_discount_amount || 0
2592                        );
2593
2594                }, this);
2595
2596
2597            /*
2598            |--------------------------------------------------------------------------
2599            | SHIPPING
2600            |--------------------------------------------------------------------------
2601            */
2602
2603            document
2604                .querySelectorAll(
2605                    '[data-checkout-shipping]'
2606                )
2607                .forEach(function (element) {
2608
2609                    element.textContent =
2610                        this.formatMoney(
2611                            result.shipping_amount || 0
2612                        );
2613
2614                }, this);
2615
2616
2617            /*
2618            |--------------------------------------------------------------------------
2619            | TAX
2620            |--------------------------------------------------------------------------
2621            */
2622
2623            document
2624                .querySelectorAll(
2625                    '[data-checkout-tax]'
2626                )
2627                .forEach(function (element) {
2628
2629                    element.textContent =
2630                        this.formatMoney(
2631                            result.tax_amount || 0
2632                        );
2633
2634                }, this);
2635
2636
2637            /*
2638            |--------------------------------------------------------------------------
2639            | FINAL TOTAL
2640            |--------------------------------------------------------------------------
2641            */
2642
2643            document
2644                .querySelectorAll(
2645                    '[data-checkout-total]'
2646                )
2647                .forEach(function (element) {
2648
2649                    element.textContent =
2650                        this.formatMoney(
2651                            result.total_amount || 0
2652                        );
2653
2654                }, this);
2655
2656
2657            /*
2658            |--------------------------------------------------------------------------
2659            | GENERIC TOTAL
2660            |--------------------------------------------------------------------------
2661            |
2662            | اگر Checkout فعلی از همان selector قبلی
2663            | استفاده می‌کند.
2664            |
2665            */
2666
2667            document
2668                .querySelectorAll(
2669                    '[data-cart-total]'
2670                )
2671                .forEach(function (element) {
2672
2673                    element.textContent =
2674                        this.formatMoney(
2675                            result.total_amount || 0
2676                        );
2677
2678                }, this);
2679        },
2680
2681
2682        /*
2683        |--------------------------------------------------------------------------
2684        | SHOW APPLIED COUPON
2685        |--------------------------------------------------------------------------
2686        */
2687
2688        showAppliedCoupon: function (
2689            coupon
2690        ) {
2691
2692            const box =
2693                document.getElementById(
2694                    'shop_coupon_applied'
2695                );
2696
2697            const title =
2698                document.getElementById(
2699                    'shop_coupon_title'
2700                );
2701
2702            const input =
2703                document.getElementById(
2704                    'shop_coupon_code'
2705                );
2706
2707            const applyButton =
2708                document.getElementById(
2709                    'shop_coupon_apply'
2710                );
2711
2712
2713            if (title) {
2714
2715                title.textContent =
2716                    coupon &&
2717                        coupon.title
2718                        ? coupon.title
2719                        : (
2720                            coupon &&
2721                                coupon.code
2722                                ? coupon.code
2723                                : 'کد تخفیف اعمال شد.'
2724                        );
2725            }
2726
2727
2728            if (input) {
2729                input.value =
2730                    coupon &&
2731                        coupon.code
2732                        ? coupon.code
2733                        : '';
2734                input.disabled = true;
2735            }
2736
2737
2738            if (applyButton) {
2739                applyButton.hidden = true;
2740            }
2741
2742
2743            if (box) {
2744                box.hidden = false;
2745            }
2746        },
2747
2748
2749        /*
2750        |--------------------------------------------------------------------------
2751        | HIDE APPLIED COUPON
2752        |--------------------------------------------------------------------------
2753        */
2754
2755        hideAppliedCoupon: function () {
2756
2757            const box =
2758                document.getElementById(
2759                    'shop_coupon_applied'
2760                );
2761
2762            const input =
2763                document.getElementById(
2764                    'shop_coupon_code'
2765                );
2766
2767            const applyButton =
2768                document.getElementById(
2769                    'shop_coupon_apply'
2770                );
2771
2772
2773            if (input) {
2774
2775                input.value = '';
2776                input.disabled = false;
2777            }
2778
2779
2780            if (applyButton) {
2781                applyButton.hidden = false;
2782            }
2783
2784
2785            if (box) {
2786                box.hidden = true;
2787            }
2788        },
2789
2790
2791        /*
2792        |--------------------------------------------------------------------------
2793        | APPLY COUPON
2794        |--------------------------------------------------------------------------
2795        */
2796
2797        applyCoupon: async function () {
2798
2799            const session =
2800                this.getCheckoutSession();
2801
2802            if (!session) {
2803
2804                this.showCouponMessage(
2805                    'اطلاعات Checkout پیدا نشد. لطفاً صفحه را دوباره بارگذاری کنید.',
2806                    'error'
2807                );
2808
2809                return null;
2810            }
2811
2812
2813            const couponCode =
2814                this.getCouponCode();
2815
2816
2817            if (!couponCode) {
2818
2819                this.showCouponMessage(
2820                    'کد تخفیف را وارد کنید.',
2821                    'error'
2822                );
2823
2824                const input =
2825                    document.getElementById(
2826                        'shop_coupon_code'
2827                    );
2828
2829                if (input) {
2830                    input.focus();
2831                }
2832
2833                return null;
2834            }
2835
2836
2837            if (
2838                this.setCouponLoading(true) === false
2839            ) {
2840                return null;
2841            }
2842
2843
2844            this.clearCouponMessage();
2845
2846
2847            try {
2848
2849                const result =
2850                    await this.requestToCheckout(
2851                        {
2852                            action: 'coupon_apply',
2853
2854                            checkout_session_id:
2855                                session.id,
2856
2857                            checkout_session_token:
2858                                session.token,
2859
2860                            coupon_code:
2861                                couponCode
2862                        }
2863                    );
2864
2865
2866                /*
2867                |--------------------------------------------------------------------------
2868                | UPDATE TOTALS
2869                |--------------------------------------------------------------------------
2870                */
2871
2872                this.handleCouponCheckout(
2873                    result
2874                );
2875
2876
2877                /*
2878                |--------------------------------------------------------------------------
2879                | SHOW COUPON
2880                |--------------------------------------------------------------------------
2881                */
2882
2883                this.showAppliedCoupon(
2884                    result.coupon || {
2885                        code: couponCode
2886                    }
2887                );
2888
2889
2890                /*
2891                |--------------------------------------------------------------------------
2892                | MESSAGE
2893                |--------------------------------------------------------------------------
2894                */
2895
2896                this.showCouponMessage(
2897                    result.message ||
2898                    'کد تخفیف با موفقیت اعمال شد.',
2899                    'success'
2900                );
2901
2902
2903                /*
2904                |--------------------------------------------------------------------------
2905                | EVENT
2906                |--------------------------------------------------------------------------
2907                */
2908
2909                this.dispatchCartEvent(
2910                    'loxshop:coupon-applied',
2911                    result
2912                );
2913
2914
2915                return result;
2916
2917
2918            } catch (error) {
2919
2920                console.error(
2921                    'LOX SHOP COUPON APPLY ERROR:',
2922                    error
2923                );
2924
2925
2926                this.showCouponMessage(
2927                    error.message ||
2928                    'اعمال کد تخفیف انجام نشد.',
2929                    'error'
2930                );
2931
2932
2933                return null;
2934
2935
2936            } finally {
2937
2938                this.setCouponLoading(false);
2939            }
2940        },
2941
2942
2943        /*
2944        |--------------------------------------------------------------------------
2945        | REMOVE COUPON
2946        |--------------------------------------------------------------------------
2947        */
2948
2949        removeCoupon: async function () {
2950
2951            const session =
2952                this.getCheckoutSession();
2953
2954
2955            if (!session) {
2956
2957                this.showCouponMessage(
2958                    'اطلاعات Checkout پیدا نشد. لطفاً صفحه را دوباره بارگذاری کنید.',
2959                    'error'
2960                );
2961
2962                return null;
2963            }
2964
2965
2966            if (
2967                this.setCouponLoading(true) === false
2968            ) {
2969                return null;
2970            }
2971
2972
2973            this.clearCouponMessage();
2974
2975
2976            try {
2977
2978                const result =
2979                    await this.requestToCheckout(
2980                        {
2981                            action: 'coupon_remove',
2982
2983                            checkout_session_id:
2984                                session.id,
2985
2986                            checkout_session_token:
2987                                session.token
2988                        }
2989                    );
2990
2991
2992                /*
2993                |--------------------------------------------------------------------------
2994                | UPDATE TOTALS
2995                |--------------------------------------------------------------------------
2996                */
2997
2998                this.handleCouponCheckout(
2999                    result
3000                );
3001
3002
3003                /*
3004                |--------------------------------------------------------------------------
3005                | HIDE COUPON
3006                |--------------------------------------------------------------------------
3007                */
3008
3009                this.hideAppliedCoupon();
3010
3011
3012                /*
3013                |--------------------------------------------------------------------------
3014                | MESSAGE
3015                |--------------------------------------------------------------------------
3016                */
3017
3018                this.showCouponMessage(
3019                    result.message ||
3020                    'کد تخفیف حذف شد.',
3021                    'success'
3022                );
3023
3024
3025                /*
3026                |--------------------------------------------------------------------------
3027                | EVENT
3028                |--------------------------------------------------------------------------
3029                */
3030
3031                this.dispatchCartEvent(
3032                    'loxshop:coupon-removed',
3033                    result
3034                );
3035
3036
3037                return result;
3038
3039
3040            } catch (error) {
3041
3042                console.error(
3043                    'LOX SHOP COUPON REMOVE ERROR:',
3044                    error
3045                );
3046
3047
3048                this.showCouponMessage(
3049                    error.message ||
3050                    'حذف کد تخفیف انجام نشد.',
3051                    'error'
3052                );
3053
3054
3055                return null;
3056
3057
3058            } finally {
3059
3060                this.setCouponLoading(false);
3061            }
3062        },
3063
3064
3065        /*
3066        |--------------------------------------------------------------------------
3067        | CHECKOUT REQUEST
3068        |--------------------------------------------------------------------------
3069        |
3070        | coupon_apply / coupon_remove
3071        | به /coreshop/ajax.php می‌روند.
3072        |
3073        */
3074
3075        requestToCheckout: async function (
3076            data
3077        ) {
3078
3079            let response;
3080
3081
3082            try {
3083
3084                response =
3085                    await fetch(
3086                        '/coreshop/ajax.php',
3087                        {
3088                            method: 'POST',
3089
3090                            headers: {
3091                                'Content-Type':
3092                                    'application/json',
3093
3094                                'X-Requested-With':
3095                                    'XMLHttpRequest'
3096                            },
3097
3098                            credentials:
3099                                'same-origin',
3100
3101                            body:
3102                                JSON.stringify(data)
3103                        }
3104                    );
3105
3106
3107            } catch (error) {
3108
3109                throw {
3110                    success: false,
3111                    error: 'network_error',
3112                    message: 'ارتباط با سرور برقرار نشد.'
3113                };
3114            }
3115
3116
3117            let result;
3118
3119
3120            try {
3121
3122                result =
3123                    await response.json();
3124
3125            } catch (error) {
3126
3127                throw {
3128                    success: false,
3129                    error: 'invalid_response',
3130                    message: 'پاسخ سرور معتبر نیست.'
3131                };
3132            }
3133
3134
3135            if (
3136                !response.ok ||
3137                !result ||
3138                result.success !== true
3139            ) {
3140
3141                throw result || {
3142                    success: false,
3143                    error: 'server_error',
3144                    message: 'خطایی در سرور رخ داده است.'
3145                };
3146            }
3147
3148
3149            return result;
3150        },
3151
3152
3153        /*
3154        |--------------------------------------------------------------------------
3155        | FORMAT MONEY
3156        |--------------------------------------------------------------------------
3157        */
3158
3159        formatMoney: function (
3160            value
3161        ) {
3162
3163            value =
3164                Number(
3165                    value || 0
3166                );
3167
3168
3169            if (!isFinite(value)) {
3170                value = 0;
3171            }
3172
3173
3174            return value.toLocaleString(
3175                'en-US'
3176            );
3177        },
3178
3179
3180        /*
3181        |--------------------------------------------------------------------------
3182        | DISPATCH EVENT
3183        |--------------------------------------------------------------------------
3184        */
3185
3186        dispatchCartEvent: function (
3187            name,
3188            detail
3189        ) {
3190
3191            try {
3192
3193                document.dispatchEvent(
3194                    new CustomEvent(
3195                        name,
3196                        {
3197                            detail: detail
3198                        }
3199                    )
3200                );
3201
3202            } catch (error) {
3203
3204                /*
3205                |--------------------------------------------------------------------------
3206                | Browser fallback
3207                |--------------------------------------------------------------------------
3208                */
3209            }
3210        }
3211
3212    };
3213
3214
3215    /*
3216    |--------------------------------------------------------------------------
3217    | APPLY COUPON BUTTON
3218    |--------------------------------------------------------------------------
3219    */
3220
3221    document.addEventListener(
3222        'click',
3223        function (event) {
3224
3225            const button =
3226                event.target.closest(
3227                    '#shop_coupon_apply'
3228                );
3229
3230
3231            if (!button) {
3232                return;
3233            }
3234
3235
3236            event.preventDefault();
3237
3238
3239            ShopCart.applyCoupon();
3240        }
3241    );
3242
3243
3244    /*
3245    |--------------------------------------------------------------------------
3246    | REMOVE COUPON BUTTON
3247    |--------------------------------------------------------------------------
3248    */
3249
3250    document.addEventListener(
3251        'click',
3252        function (event) {
3253
3254            const button =
3255                event.target.closest(
3256                    '#shop_coupon_remove'
3257                );
3258
3259
3260            if (!button) {
3261                return;
3262            }
3263
3264
3265            event.preventDefault();
3266
3267
3268            ShopCart.removeCoupon();
3269        }
3270    );
3271
3272
3273    /*
3274    |--------------------------------------------------------------------------
3275    | ENTER KEY
3276    |--------------------------------------------------------------------------
3277    |
3278    | کاربر می‌تواند کد را با Enter نیز اعمال کند.
3279    |
3280    */
3281
3282    document.addEventListener(
3283        'keydown',
3284        function (event) {
3285
3286            if (
3287                event.key !== 'Enter'
3288            ) {
3289                return;
3290            }
3291
3292
3293            const input =
3294                event.target.closest(
3295                    '#shop_coupon_code'
3296                );
3297
3298
3299            if (!input) {
3300                return;
3301            }
3302
3303
3304            event.preventDefault();
3305
3306
3307            ShopCart.applyCoupon();
3308        }
3309    );
3310
3311
3312    /*
3313    |--------------------------------------------------------------------------
3314    | GLOBAL EXPORT
3315    |--------------------------------------------------------------------------
3316    */
3317
3318    window.LoxShopCart =
3319        ShopCart;
3320
3321
3322})(window, document);
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369(function () {
3370
3371    'use strict';
3372
3373
3374    /*
3375    |--------------------------------------------------------------------------
3376    | CONFIG
3377    |--------------------------------------------------------------------------
3378    */
3379
3380    var OTP_SECONDS = 900;
3381
3382    var AJAX_URL =
3383        '/shoporder/customer_auth.php';
3384
3385
3386    /*
3387    |--------------------------------------------------------------------------
3388    | ROOT
3389    |--------------------------------------------------------------------------
3390    */
3391
3392    var authWrapper =
3393        document.getElementById(
3394            'shopAuth'
3395        );
3396
3397    if (!authWrapper) {
3398        return;
3399    }
3400
3401
3402    var WID =
3403        parseInt(
3404            authWrapper.getAttribute('data-wid'),
3405            10
3406        ) || 0;
3407
3408
3409    /*
3410    |--------------------------------------------------------------------------
3411    | ELEMENTS
3412    |--------------------------------------------------------------------------
3413    */
3414
3415    var mobileStep =
3416        document.getElementById(
3417            'shopAuthMobileStep'
3418        );
3419
3420    var otpStep =
3421        document.getElementById(
3422            'shopAuthOtpStep'
3423        );
3424
3425    var mobileForm =
3426        document.getElementById(
3427            'shopAuthMobileForm'
3428        );
3429
3430    var otpForm =
3431        document.getElementById(
3432            'shopAuthOtpForm'
3433        );
3434
3435    var mobileInput =
3436        document.getElementById(
3437            'shopAuthMobile'
3438        );
3439
3440    var otpInput =
3441        document.getElementById(
3442            'shopAuthOtp'
3443        );
3444
3445    var mobilePreview =
3446        document.getElementById(
3447            'shopAuthMobilePreview'
3448        );
3449
3450    var sendButton =
3451        document.getElementById(
3452            'shopAuthSendButton'
3453        );
3454
3455    var verifyButton =
3456        document.getElementById(
3457            'shopAuthVerifyButton'
3458        );
3459
3460    var resendButton =
3461        document.getElementById(
3462            'shopAuthResendButton'
3463        );
3464
3465    var changeMobileButton =
3466        document.getElementById(
3467            'shopAuthChangeMobile'
3468        );
3469
3470    var mobileError =
3471        document.getElementById(
3472            'shopAuthMobileError'
3473        );
3474
3475    var otpError =
3476        document.getElementById(
3477            'shopAuthOtpError'
3478        );
3479
3480    var timer =
3481        document.getElementById(
3482            'shopAuthTimer'
3483        );
3484
3485    var timerWrapper =
3486        document.getElementById(
3487            'shopAuthTimerWrapper'
3488        );
3489
3490    var sendText =
3491        document.querySelector(
3492            '.shop-auth-send-text'
3493        );
3494
3495    var sendLoading =
3496        document.querySelector(
3497            '.shop-auth-send-loading'
3498        );
3499
3500    var verifyText =
3501        document.querySelector(
3502            '.shop-auth-verify-text'
3503        );
3504
3505    var verifyLoading =
3506        document.querySelector(
3507            '.shop-auth-verify-loading'
3508        );
3509
3510
3511    /*
3512    |--------------------------------------------------------------------------
3513    | STATE
3514    |--------------------------------------------------------------------------
3515    */
3516
3517    var currentMobile = '';
3518
3519    var timerInterval = null;
3520
3521    var remainingSeconds = 0;
3522
3523
3524    /*
3525    |--------------------------------------------------------------------------
3526    | NORMALIZE MOBILE
3527    |--------------------------------------------------------------------------
3528    */
3529
3530    function normalizeMobile(value) {
3531
3532        value = String(value || '');
3533
3534
3535        value = value.replace(
3536            /[Û°-Û¹]/g,
3537            function (char) {
3538
3539                return String(
3540                    'Û°Û±Û²Û³Û´ÛµÛ¶Û·Û¸Û¹'.indexOf(char)
3541                );
3542
3543            }
3544        );
3545
3546
3547        value = value.replace(
3548            /[Ù -Ù©]/g,
3549            function (char) {
3550
3551                return String(
3552                    '٠١٢٣٤٥٦٧٨٩'.indexOf(char)
3553                );
3554
3555            }
3556        );
3557
3558
3559        value = value.replace(
3560            /[^0-9+]/g,
3561            ''
3562        );
3563
3564
3565        if (
3566            value.indexOf('+98') === 0
3567        ) {
3568
3569            value =
3570                '0' +
3571                value.substring(3);
3572
3573        }
3574        else if (
3575            value.indexOf('98') === 0
3576        ) {
3577
3578            value =
3579                '0' +
3580                value.substring(2);
3581
3582        }
3583        else if (
3584            value.length === 10
3585            &&
3586            value.charAt(0) === '9'
3587        ) {
3588
3589            value =
3590                '0' +
3591                value;
3592
3593        }
3594
3595
3596        return value;
3597
3598    }
3599
3600
3601    /*
3602    |--------------------------------------------------------------------------
3603    | VALIDATE MOBILE
3604    |--------------------------------------------------------------------------
3605    */
3606
3607    function isValidMobile(mobile) {
3608
3609        return /^09[0-9]{9}$/.test(
3610            mobile
3611        );
3612
3613    }
3614
3615
3616    /*
3617    |--------------------------------------------------------------------------
3618    | ERROR
3619    |--------------------------------------------------------------------------
3620    */
3621
3622    function showError(
3623        element,
3624        message
3625    ) {
3626
3627        element.textContent =
3628            message || '';
3629
3630        element.classList.remove(
3631            'd-none'
3632        );
3633
3634    }
3635
3636
3637    function hideError(element) {
3638
3639        element.textContent = '';
3640
3641        element.classList.add(
3642            'd-none'
3643        );
3644
3645    }
3646
3647
3648    /*
3649    |--------------------------------------------------------------------------
3650    | SEND LOADING
3651    |--------------------------------------------------------------------------
3652    */
3653
3654    function setSendLoading(loading) {
3655
3656        sendButton.disabled =
3657            loading;
3658
3659        if (loading) {
3660
3661            sendText.classList.add(
3662                'd-none'
3663            );
3664
3665            sendLoading.classList.remove(
3666                'd-none'
3667            );
3668
3669        }
3670        else {
3671
3672            sendText.classList.remove(
3673                'd-none'
3674            );
3675
3676            sendLoading.classList.add(
3677                'd-none'
3678            );
3679
3680        }
3681
3682    }
3683
3684
3685    /*
3686    |--------------------------------------------------------------------------
3687    | VERIFY LOADING
3688    |--------------------------------------------------------------------------
3689    */
3690
3691    function setVerifyLoading(loading) {
3692
3693        verifyButton.disabled =
3694            loading;
3695
3696        if (loading) {
3697
3698            verifyText.classList.add(
3699                'd-none'
3700            );
3701
3702            verifyLoading.classList.remove(
3703                'd-none'
3704            );
3705
3706        }
3707        else {
3708
3709            verifyText.classList.remove(
3710                'd-none'
3711            );
3712
3713            verifyLoading.classList.add(
3714                'd-none'
3715            );
3716
3717        }
3718
3719    }
3720
3721
3722    /*
3723    |--------------------------------------------------------------------------
3724    | TIMER
3725    |--------------------------------------------------------------------------
3726    */
3727
3728    function formatTime(seconds) {
3729
3730        var minutes =
3731            Math.floor(
3732                seconds / 60
3733            );
3734
3735        var secs =
3736            seconds % 60;
3737
3738
3739        return (
3740            String(minutes).padStart(2, '0')
3741            +
3742            ':'
3743            +
3744            String(secs).padStart(2, '0')
3745        );
3746
3747    }
3748
3749
3750    function stopTimer() {
3751
3752        if (
3753            timerInterval !== null
3754        ) {
3755
3756            clearInterval(
3757                timerInterval
3758            );
3759
3760            timerInterval = null;
3761
3762        }
3763
3764    }
3765
3766
3767    function startTimer(seconds) {
3768
3769        stopTimer();
3770
3771
3772        remainingSeconds =
3773            parseInt(
3774                seconds,
3775                10
3776            ) || OTP_SECONDS;
3777
3778
3779        timer.textContent =
3780            formatTime(
3781                remainingSeconds
3782            );
3783
3784
3785        timerWrapper.classList.remove(
3786            'd-none'
3787        );
3788
3789        resendButton.classList.add(
3790            'd-none'
3791        );
3792
3793
3794        timerInterval =
3795            setInterval(
3796                function () {
3797
3798                    remainingSeconds--;
3799
3800
3801                    if (
3802                        remainingSeconds <= 0
3803                    ) {
3804
3805                        remainingSeconds = 0;
3806
3807                        timer.textContent =
3808                            '00:00';
3809
3810                        stopTimer();
3811
3812
3813                        timerWrapper.classList.add(
3814                            'd-none'
3815                        );
3816
3817                        resendButton.classList.remove(
3818                            'd-none'
3819                        );
3820
3821                        return;
3822
3823                    }
3824
3825
3826                    timer.textContent =
3827                        formatTime(
3828                            remainingSeconds
3829                        );
3830
3831                },
3832                1000
3833            );
3834
3835    }
3836
3837
3838    /*
3839    |--------------------------------------------------------------------------
3840    | SHOW OTP
3841    |--------------------------------------------------------------------------
3842    */
3843
3844    function showOtpStep() {
3845
3846        mobileStep.classList.add(
3847            'd-none'
3848        );
3849
3850        otpStep.classList.remove(
3851            'd-none'
3852        );
3853
3854
3855        mobilePreview.textContent =
3856            currentMobile;
3857
3858
3859        otpInput.value = '';
3860
3861
3862        setTimeout(
3863            function () {
3864
3865                otpInput.focus();
3866
3867            },
3868            100
3869        );
3870
3871    }
3872
3873
3874    /*
3875    |--------------------------------------------------------------------------
3876    | SHOW MOBILE
3877    |--------------------------------------------------------------------------
3878    */
3879
3880    function showMobileStep() {
3881
3882        stopTimer();
3883
3884
3885        otpStep.classList.add(
3886            'd-none'
3887        );
3888
3889        mobileStep.classList.remove(
3890            'd-none'
3891        );
3892
3893
3894        otpInput.value = '';
3895
3896        hideError(
3897            otpError
3898        );
3899
3900        mobileInput.focus();
3901
3902    }
3903
3904
3905    /*
3906    |--------------------------------------------------------------------------
3907    | SEND OTP
3908    |--------------------------------------------------------------------------
3909    */
3910
3911    function sendOtp() {
3912
3913        var mobile =
3914            normalizeMobile(
3915                mobileInput.value
3916            );
3917
3918
3919        mobileInput.value =
3920            mobile;
3921
3922
3923        hideError(
3924            mobileError
3925        );
3926
3927
3928
3929
3930
3931        if (
3932            !isValidMobile(mobile)
3933        ) {
3934
3935            showError(
3936                mobileError,
3937                'لطفاً شماره موبایل معتبر وارد کنید.'
3938            );
3939
3940            mobileInput.focus();
3941
3942            return;
3943
3944        }
3945
3946
3947        currentMobile =
3948            mobile;
3949
3950
3951        setSendLoading(true);
3952
3953
3954        var formData =
3955            new FormData();
3956
3957
3958        /*
3959         * مهم:
3960         * PHP هم action را می‌خواند.
3961         */
3962
3963        formData.append(
3964            'action',
3965            'send_code'
3966        );
3967
3968
3969        formData.append(
3970            'wid',
3971            String(WID)
3972        );
3973
3974
3975        formData.append(
3976            'mobile',
3977            mobile
3978        );
3979
3980
3981        fetch(
3982            AJAX_URL,
3983            {
3984                method: 'POST',
3985                body: formData,
3986                credentials: 'same-origin'
3987            }
3988        )
3989            .then(
3990                function (response) {
3991
3992                    if (!response.ok) {
3993
3994                        throw new Error(
3995                            'HTTP ' +
3996                            response.status
3997                        );
3998
3999                    }
4000
4001                    return response.json();
4002
4003                }
4004            )
4005            .then(
4006                function (result) {
4007
4008                    setSendLoading(false);
4009
4010
4011                    /*
4012                     * فقط ok و already_sent
4013                     * اجازه ورود به مرحله OTP دارند.
4014                     */
4015
4016                    if (
4017                        !result
4018                        ||
4019                        (
4020                            result.status !== 'ok'
4021                            &&
4022                            result.status !== 'already_sent'
4023                        )
4024                    ) {
4025
4026                        showError(
4027                            mobileError,
4028                            result
4029                                && result.message
4030                                ? result.message
4031                                : 'ارسال کد تأیید انجام نشد.'
4032                        );
4033
4034                        return;
4035
4036                    }
4037
4038
4039                    showOtpStep();
4040
4041
4042                    startTimer(
4043                        result.expires_in
4044                        || OTP_SECONDS
4045                    );
4046
4047                }
4048            )
4049            .catch(
4050                function (error) {
4051
4052                    console.error(
4053                        'SHOP AUTH SEND ERROR:',
4054                        error
4055                    );
4056
4057
4058                    setSendLoading(false);
4059
4060
4061                    showError(
4062                        mobileError,
4063                        'ارتباط با سرور برقرار نشد. دوباره تلاش کنید.'
4064                    );
4065
4066                }
4067            );
4068
4069    }
4070
4071
4072    /*
4073    |--------------------------------------------------------------------------
4074    | VERIFY OTP
4075    |--------------------------------------------------------------------------
4076    */
4077
4078    function verifyOtp() {
4079
4080        var code =
4081            otpInput.value
4082                .replace(
4083                    /[^0-9]/g,
4084                    ''
4085                );
4086
4087
4088        otpInput.value =
4089            code;
4090
4091
4092        hideError(
4093            otpError
4094        );
4095
4096
4097
4098
4099
4100        if (
4101            !currentMobile
4102        ) {
4103
4104            showError(
4105                otpError,
4106                'شماره موبایل مشخص نیست.'
4107            );
4108
4109            return;
4110
4111        }
4112
4113
4114        if (
4115            code.length !== 4
4116        ) {
4117
4118            showError(
4119                otpError,
4120                'کد تأیید باید ۶ رقمی باشد.'
4121            );
4122
4123            otpInput.focus();
4124
4125            return;
4126
4127        }
4128
4129
4130        setVerifyLoading(true);
4131
4132
4133        var formData =
4134            new FormData();
4135
4136
4137        formData.append(
4138            'action',
4139            'verify_code'
4140        );
4141
4142
4143        formData.append(
4144            'wid',
4145            String(WID)
4146        );
4147
4148
4149        formData.append(
4150            'mobile',
4151            currentMobile
4152        );
4153
4154
4155        formData.append(
4156            'code',
4157            code
4158        );
4159
4160
4161        fetch(
4162            AJAX_URL,
4163            {
4164                method: 'POST',
4165                body: formData,
4166                credentials: 'same-origin'
4167            }
4168        )
4169            .then(
4170                function (response) {
4171
4172                    if (!response.ok) {
4173
4174                        throw new Error(
4175                            'HTTP ' +
4176                            response.status
4177                        );
4178
4179                    }
4180
4181                    return response.json();
4182
4183                }
4184            )
4185            .then(
4186                function (result) {
4187
4188                    setVerifyLoading(false);
4189
4190
4191                    if (
4192                        !result
4193                        ||
4194                        result.status !== 'ok'
4195                    ) {
4196
4197                        showError(
4198                            otpError,
4199                            result
4200                                && result.message
4201                                ? result.message
4202                                : 'کد تأیید صحیح نیست.'
4203                        );
4204
4205                        return;
4206
4207                    }
4208
4209
4210                    /*
4211                     * Login/Register موفق
4212                     */
4213
4214                    stopTimer();
4215
4216
4217                    if (
4218                        result.redirect
4219                    ) {
4220
4221                        window.location.href =
4222                            result.redirect;
4223
4224                        return;
4225
4226                    }
4227
4228
4229                    /*
4230                     * در حالت عادی صفحه Reload می‌شود
4231                     * و Session جدید Customer را می‌بیند.
4232                     */
4233
4234                    window.location.reload();
4235
4236                }
4237            )
4238            .catch(
4239                function (error) {
4240
4241                    console.error(
4242                        'SHOP AUTH VERIFY ERROR:',
4243                        error
4244                    );
4245
4246
4247                    setVerifyLoading(false);
4248
4249
4250                    showError(
4251                        otpError,
4252                        'ارتباط با سرور برقرار نشد. دوباره تلاش کنید.'
4253                    );
4254
4255                }
4256            );
4257
4258    }
4259
4260
4261    /*
4262    |--------------------------------------------------------------------------
4263    | RESEND
4264    |--------------------------------------------------------------------------
4265    */
4266
4267    function resendOtp() {
4268
4269        if (
4270            !currentMobile
4271        ) {
4272
4273            showMobileStep();
4274
4275            return;
4276
4277        }
4278
4279
4280        mobileInput.value =
4281            currentMobile;
4282
4283
4284        sendOtp();
4285
4286    }
4287
4288
4289    /*
4290    |--------------------------------------------------------------------------
4291    | CHANGE MOBILE
4292    |--------------------------------------------------------------------------
4293    */
4294
4295    changeMobileButton.addEventListener(
4296        'click',
4297        function () {
4298
4299            currentMobile = '';
4300
4301            showMobileStep();
4302
4303        }
4304    );
4305
4306
4307    /*
4308    |--------------------------------------------------------------------------
4309    | FORMS
4310    |--------------------------------------------------------------------------
4311    */
4312
4313    mobileForm.addEventListener(
4314        'submit',
4315        function (event) {
4316
4317            event.preventDefault();
4318
4319            sendOtp();
4320
4321        }
4322    );
4323
4324
4325    otpForm.addEventListener(
4326        'submit',
4327        function (event) {
4328
4329            event.preventDefault();
4330
4331            verifyOtp();
4332
4333        }
4334    );
4335
4336
4337    /*
4338    |--------------------------------------------------------------------------
4339    | RESEND
4340    |--------------------------------------------------------------------------
4341    */
4342
4343    resendButton.addEventListener(
4344        'click',
4345        function () {
4346
4347            resendButton.disabled =
4348                true;
4349
4350
4351            resendOtp();
4352
4353
4354            setTimeout(
4355                function () {
4356
4357                    resendButton.disabled =
4358                        false;
4359
4360                },
4361                1000
4362            );
4363
4364        }
4365    );
4366
4367
4368    /*
4369    |--------------------------------------------------------------------------
4370    | OTP INPUT
4371    |--------------------------------------------------------------------------
4372    */
4373
4374    otpInput.addEventListener(
4375        'input',
4376        function () {
4377
4378            this.value =
4379                this.value
4380                    .replace(
4381                        /[Û°-Û¹]/g,
4382                        function (char) {
4383
4384                            return String(
4385                                'Û°Û±Û²Û³Û´ÛµÛ¶Û·Û¸Û¹'
4386                                    .indexOf(char)
4387                            );
4388
4389                        }
4390                    )
4391                    .replace(
4392                        /[Ù -Ù©]/g,
4393                        function (char) {
4394
4395                            return String(
4396                                '٠١٢٣٤٥٦٧٨٩'
4397                                    .indexOf(char)
4398                            );
4399
4400                        }
4401                    )
4402                    .replace(
4403                        /[^0-9]/g,
4404                        ''
4405                    )
4406                    .substring(
4407                        0,
4408                        6
4409                    );
4410
4411        }
4412    );
4413
4414
4415    /*
4416    |--------------------------------------------------------------------------
4417    | MOBILE INPUT
4418    |--------------------------------------------------------------------------
4419    */
4420
4421    mobileInput.addEventListener(
4422        'input',
4423        function () {
4424
4425            var value =
4426                normalizeMobile(
4427                    this.value
4428                );
4429
4430
4431            this.value =
4432                value.substring(
4433                    0,
4434                    11
4435                );
4436
4437        }
4438    );
4439
4440
4441})();
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481(function (window, document) {
4482    'use strict';
4483    /*
4484    |--------------------------------------------------------------------------
4485    | LOXBLOG SHOP CART
4486    |--------------------------------------------------------------------------
4487    */
4488    const ShopCart = {
4489        endpoint: '/shoporder/checkorder.php',
4490        /*
4491        |--------------------------------------------------------------------------
4492        | AJAX REQUEST
4493        |--------------------------------------------------------------------------
4494        */
4495        request: async function (data) {
4496            let response;
4497            try {
4498                response = await fetch(
4499                    this.endpoint,
4500                    {
4501                        method: 'POST',
4502                        headers: {
4503                            'Content-Type': 'application/json',
4504                            'X-Requested-With': 'XMLHttpRequest'
4505                        },
4506                        credentials: 'same-origin',
4507                        body: JSON.stringify(data)
4508                    }
4509                );
4510            } catch (error) {
4511                throw {
4512                    success: false,
4513                    error: 'network_error',
4514                    message: 'ارتباط با سرور برقرار نشد.'
4515                };
4516            }
4517            /*
4518            |--------------------------------------------------------------------------
4519            | READ JSON
4520            |--------------------------------------------------------------------------
4521            */
4522            let result;
4523            try {
4524                result = await response.json();
4525            } catch (error) {
4526                throw {
4527                    success: false,
4528                    error: 'invalid_response',
4529                    message: 'پاسخ سرور معتبر نیست.'
4530                };
4531            }
4532            /*
4533            |--------------------------------------------------------------------------
4534            | PHP ERROR
4535            |--------------------------------------------------------------------------
4536            */
4537            if (
4538                !response.ok ||
4539                !result ||
4540                result.success !== true
4541            ) {
4542                throw result || {
4543                    success: false,
4544                    error: 'server_error',
4545                    message: 'خطایی در سرور رخ داده است.'
4546                };
4547            }
4548            return result;
4549        },
4550        /*
4551        |--------------------------------------------------------------------------
4552        | GET FORM
4553        |--------------------------------------------------------------------------
4554        */
4555        getForm: function (element) {
4556            if (!element) {
4557                return null;
4558            }
4559            /*
4560            | خود فرم
4561            */
4562            if (
4563                element.tagName &&
4564                element.tagName.toLowerCase() === 'form'
4565            ) {
4566                return element;
4567            }
4568            /*
4569            | دکمه / input / element
4570            */
4571            if (element.closest) {
4572                const form = element.closest(
4573                    'form.product-cart-form, form[data-shop-cart-form], form'
4574                );
4575                if (form) {
4576                    return form;
4577                }
4578            }
4579            return null;
4580        },
4581        /*
4582        |--------------------------------------------------------------------------
4583        | GET PRODUCT ID
4584        |--------------------------------------------------------------------------
4585        */
4586        getProductId: function (form) {
4587            if (!form) {
4588                return 0;
4589            }
4590            /*
4591            | input[name="product_id"]
4592            */
4593            const input = form.querySelector(
4594                'input[name="product_id"]'
4595            );
4596            if (input) {
4597                const productId = parseInt(
4598                    input.value || 0,
4599                    10
4600                );
4601                if (productId > 0) {
4602                    return productId;
4603                }
4604            }
4605            /*
4606            | data-product-id
4607            */
4608            const dataId = parseInt(
4609                form.getAttribute('data-product-id') || 0,
4610                10
4611            );
4612            if (dataId > 0) {
4613                return dataId;
4614            }
4615            return 0;
4616        },
4617        /*
4618        |--------------------------------------------------------------------------
4619        | GET SELECT VALUE
4620        |--------------------------------------------------------------------------
4621        */
4622        getSelectValue: function (element) {
4623            if (!element) {
4624                return null;
4625            }
4626            const value = element.value;
4627            if (
4628                value === null ||
4629                value === '' ||
4630                value === '0'
4631            ) {
4632                return null;
4633            }
4634            return value;
4635        },
4636        /*
4637        |--------------------------------------------------------------------------
4638        | COLLECT ATTRIBUTES
4639        |--------------------------------------------------------------------------
4640        |
4641        | خروجی:
4642        |
4643        | {
4644        |     "1": "73",
4645        |     "2": "91"
4646        | }
4647        |
4648        |--------------------------------------------------------------------------
4649        */
4650        collectAttributes: function (form) {
4651            const attributes = {};
4652            if (!form) {
4653                return attributes;
4654            }
4655            /*
4656            |--------------------------------------------------------------------------
4657            | SELECT
4658            |--------------------------------------------------------------------------
4659            */
4660            const selects = form.querySelectorAll(
4661                'select.product_var'
4662            );
4663            selects.forEach(function (select) {
4664                const key =
4665                    select.getAttribute('key');
4666                if (!key) {
4667                    return;
4668                }
4669                const value =
4670                    ShopCart.getSelectValue(select);
4671                if (value !== null) {
4672                    attributes[key] = value;
4673                }
4674            });
4675            /*
4676            |--------------------------------------------------------------------------
4677            | RADIO
4678            |--------------------------------------------------------------------------
4679            */
4680            const radios = form.querySelectorAll(
4681                'input.product_var[type="radio"]'
4682            );
4683            const radioKeys = {};
4684            radios.forEach(function (radio) {
4685                const key =
4686                    radio.getAttribute('key');
4687                if (!key) {
4688                    return;
4689                }
4690                if (radioKeys[key]) {
4691                    return;
4692                }
4693                radioKeys[key] = true;
4694                const checked =
4695                    form.querySelector(
4696                        'input.product_var[type="radio"][key="' +
4697                        CSS.escape(key) +
4698                        '"]:checked'
4699                    );
4700                if (
4701                    checked &&
4702                    checked.value !== '' &&
4703                    checked.value !== '0'
4704                ) {
4705                    attributes[key] =
4706                        checked.value;
4707                }
4708            });
4709            /*
4710            |--------------------------------------------------------------------------
4711            | CHECKBOX
4712            |--------------------------------------------------------------------------
4713            |
4714            | PHP فعلاً برای هر attribute فقط یک value
4715            | را پشتیبانی می‌کند.
4716            |
4717            |--------------------------------------------------------------------------
4718            */
4719            const checkboxes = form.querySelectorAll(
4720                'input.product_var[type="checkbox"]'
4721            );
4722            const checkboxKeys = {};
4723            checkboxes.forEach(function (checkbox) {
4724                const key =
4725                    checkbox.getAttribute('key');
4726                if (!key) {
4727                    return;
4728                }
4729                if (checkboxKeys[key]) {
4730                    return;
4731                }
4732                checkboxKeys[key] = true;
4733                const checked =
4734                    form.querySelector(
4735                        'input.product_var[type="checkbox"][key="' +
4736                        CSS.escape(key) +
4737                        '"]:checked'
4738                    );
4739                if (
4740                    checked &&
4741                    checked.value !== '' &&
4742                    checked.value !== '0'
4743                ) {
4744                    attributes[key] =
4745                        checked.value;
4746                }
4747            });
4748            return attributes;
4749        },
4750        /*
4751        |--------------------------------------------------------------------------
4752        | GET QUANTITY
4753        |--------------------------------------------------------------------------
4754        */
4755        getQuantity: function (form) {
4756            if (!form) {
4757                return 1;
4758            }
4759            const input =
4760                form.querySelector(
4761                    'input[name="product_number"]'
4762                );
4763            if (!input) {
4764                return 1;
4765            }
4766            let quantity =
4767                parseFloat(input.value);
4768            if (
4769                !isFinite(quantity) ||
4770                quantity <= 0
4771            ) {
4772                quantity = 1;
4773            }
4774            /*
4775            | PHP maximum
4776            */
4777            if (quantity > 999999) {
4778                quantity = 999999;
4779            }
4780            return quantity;
4781        },
4782        /*
4783        |--------------------------------------------------------------------------
4784        | VALIDATE PRODUCT
4785        |--------------------------------------------------------------------------
4786        */
4787        validateProduct: async function (element) {
4788            const form =
4789                this.getForm(element);
4790            if (!form) {
4791                throw {
4792                    success: false,
4793                    error: 'invalid_form',
4794                    message: 'فرم محصول پیدا نشد.'
4795                };
4796            }
4797            const productId =
4798                this.getProductId(form);
4799            if (!productId) {
4800                throw {
4801                    success: false,
4802                    error: 'invalid_product',
4803                    message: 'شناسه محصول مشخص نیست.'
4804                };
4805            }
4806            const attributes =
4807                this.collectAttributes(form);
4808            return await this.request({
4809                action: 'validate_product',
4810                product_id: productId,
4811                attributes: attributes
4812            });
4813        },
4814        /*
4815        |--------------------------------------------------------------------------
4816        | ADD TO CART
4817        |--------------------------------------------------------------------------
4818        */
4819        addToCart: async function (element) {
4820            let form = null;
4821            let button = null;
4822            /*
4823            |--------------------------------------------------------------------------
4824            | EVENT
4825            |--------------------------------------------------------------------------
4826            */
4827            if (
4828                element &&
4829                typeof element.preventDefault === 'function'
4830            ) {
4831                element.preventDefault();
4832                element =
4833                    element.currentTarget ||
4834                    element.target;
4835            }
4836            /*
4837            |--------------------------------------------------------------------------
4838            | BUTTON
4839            |--------------------------------------------------------------------------
4840            */
4841            if (
4842                element &&
4843                element.tagName &&
4844                element.tagName.toLowerCase() === 'button'
4845            ) {
4846                button = element;
4847                form =
4848                    this.getForm(element);
4849            }
4850            /*
4851            |--------------------------------------------------------------------------
4852            | FORM
4853            |--------------------------------------------------------------------------
4854            */
4855            else if (
4856                element &&
4857                element.tagName &&
4858                element.tagName.toLowerCase() === 'form'
4859            ) {
4860                form = element;
4861                button =
4862                    form.querySelector(
4863                        'button[type="submit"]'
4864                    );
4865            }
4866            /*
4867            |--------------------------------------------------------------------------
4868            | FALLBACK
4869            |--------------------------------------------------------------------------
4870            */
4871            if (!form) {
4872                form =
4873                    document.querySelector(
4874                        'form.product-cart-form, form[data-shop-cart-form]'
4875                    );
4876            }
4877            if (!form) {
4878                this.showError(
4879                    'فرم محصول پیدا نشد.'
4880                );
4881                return false;
4882            }
4883            /*
4884            |--------------------------------------------------------------------------
4885            | BUTTON
4886            |--------------------------------------------------------------------------
4887            */
4888            if (!button) {
4889                button =
4890                    form.querySelector(
4891                        'button[type="submit"]'
4892                    );
4893            }
4894            /*
4895            |--------------------------------------------------------------------------
4896            | PRODUCT ID
4897            |--------------------------------------------------------------------------
4898            */
4899            const productId =
4900                this.getProductId(form);
4901            if (!productId) {
4902                this.showError(
4903                    'شناسه محصول مشخص نیست.'
4904                );
4905                return false;
4906            }
4907            /*
4908            |--------------------------------------------------------------------------
4909            | ATTRIBUTES
4910            |--------------------------------------------------------------------------
4911            */
4912            const attributes =
4913                this.collectAttributes(form);
4914            /*
4915            |--------------------------------------------------------------------------
4916            | QUANTITY
4917            |--------------------------------------------------------------------------
4918            */
4919            const quantity =
4920                this.getQuantity(form);
4921            /*
4922            |--------------------------------------------------------------------------
4923            | PREVENT DOUBLE CLICK
4924            |--------------------------------------------------------------------------
4925            */
4926            if (button) {
4927                if (
4928                    button.dataset.cartLoading === '1'
4929                ) {
4930                    return false;
4931                }
4932                button.dataset.cartLoading = '1';
4933                button.disabled = true;
4934            }
4935            try {
4936                /*
4937                |--------------------------------------------------------------------------
4938                | REQUEST
4939                |--------------------------------------------------------------------------
4940                */
4941                const result =
4942                    await this.request({
4943                        action: 'add_to_cart',
4944                        product_id: productId,
4945                        quantity: quantity,
4946                        attributes: attributes
4947                    });
4948                /*
4949                |--------------------------------------------------------------------------
4950                | UPDATE CART UI
4951                |--------------------------------------------------------------------------
4952                */
4953                this.handleCartResponse(result);
4954                /*
4955                |--------------------------------------------------------------------------
4956                | SUCCESS
4957                |--------------------------------------------------------------------------
4958                */
4959
4960                this.showSuccess(
4961                    (result.message ||
4962                        'محصول با موفقیت به سبد خرید اضافه شد.') +
4963                    ' <a href="cart/">مشاهده سبد خرید</a>'
4964                );
4965                const add2cartMessage =
4966                    document.getElementById('add2cart-message');
4967
4968                if (add2cartMessage) {
4969                    add2cartMessage.classList.add('show');
4970                }
4971
4972                /*
4973                |--------------------------------------------------------------------------
4974                | CALLBACK
4975                |--------------------------------------------------------------------------
4976                */
4977                this.dispatchCartEvent(
4978                    'loxshop:cart-added',
4979                    result
4980                );
4981                return true;
4982            } catch (error) {
4983                console.error(
4984                    'LOX SHOP CART ERROR:',
4985                    error
4986                );
4987                this.showError(
4988                    error.message ||
4989                    'خطایی در افزودن محصول به سبد رخ داد.'
4990                );
4991                return false;
4992            } finally {
4993                if (button) {
4994                    button.dataset.cartLoading = '0';
4995                    button.disabled = false;
4996                }
4997            }
4998        },
4999        /*
5000        |--------------------------------------------------------------------------
5001        | GET CART
5002        |--------------------------------------------------------------------------
5003        */
5004        getCart: async function () {
5005            try {
5006                const result =
5007                    await this.request({
5008                        action: 'get_cart'
5009                    });
5010                this.handleCartResponse(
5011                    result
5012                );
5013                this.dispatchCartEvent(
5014                    'loxshop:cart-loaded',
5015                    result
5016                );
5017                return result;
5018            } catch (error) {
5019                console.error(
5020                    'LOX SHOP GET CART ERROR:',
5021                    error
5022                );
5023                this.showError(
5024                    error.message ||
5025                    'خطا در دریافت سبد خرید.'
5026                );
5027                return null;
5028            }
5029        },
5030        /*
5031        |--------------------------------------------------------------------------
5032        | UPDATE CART
5033        |--------------------------------------------------------------------------
5034        */
5035        updateCart: async function (
5036            cartItemId,
5037            quantity
5038        ) {
5039            cartItemId =
5040                parseInt(
5041                    cartItemId || 0,
5042                    10
5043                );
5044            quantity =
5045                parseFloat(
5046                    quantity
5047                );
5048            if (cartItemId <= 0) {
5049                this.showError(
5050                    'آیتم سبد معتبر نیست.'
5051                );
5052                return null;
5053            }
5054            if (
5055                !isFinite(quantity)
5056            ) {
5057                quantity = 0;
5058            }
5059            if (quantity > 999999) {
5060                quantity = 999999;
5061            }
5062            try {
5063                const result =
5064                    await this.request({
5065                        action: 'update_cart',
5066                        cart_item_id:
5067                            cartItemId,
5068                        quantity:
5069                            quantity
5070                    });
5071                this.handleCartResponse(
5072                    result
5073                );
5074                this.dispatchCartEvent(
5075                    'loxshop:cart-updated',
5076                    result
5077                );
5078                return result;
5079            } catch (error) {
5080                console.error(
5081                    'LOX SHOP UPDATE CART ERROR:',
5082                    error
5083                );
5084                this.showError(
5085                    error.message ||
5086                    'خطا در بروزرسانی سبد خرید.'
5087                );
5088                return null;
5089            }
5090        },
5091        /*
5092        |--------------------------------------------------------------------------
5093        | REMOVE FROM CART
5094        |--------------------------------------------------------------------------
5095        */
5096        removeFromCart: async function (
5097            cartItemId
5098        ) {
5099            cartItemId =
5100                parseInt(
5101                    cartItemId || 0,
5102                    10
5103                );
5104            if (cartItemId <= 0) {
5105                this.showError(
5106                    'آیتم سبد معتبر نیست.'
5107                );
5108                return null;
5109            }
5110            try {
5111                const result =
5112                    await this.request({
5113                        action:
5114                            'remove_from_cart',
5115                        cart_item_id:
5116                            cartItemId
5117                    });
5118                this.handleCartResponse(
5119                    result
5120                );
5121                this.dispatchCartEvent(
5122                    'loxshop:cart-removed',
5123                    result
5124                );
5125                return result;
5126            } catch (error) {
5127                console.error(
5128                    'LOX SHOP REMOVE CART ERROR:',
5129                    error
5130                );
5131                this.showError(
5132                    error.message ||
5133                    'خطا در حذف کالا.'
5134                );
5135                return null;
5136            }
5137        },
5138        /*
5139        |--------------------------------------------------------------------------
5140        | CLEAR CART
5141        |--------------------------------------------------------------------------
5142        */
5143        clearCart: async function () {
5144            try {
5145                const result =
5146                    await this.request({
5147                        action:
5148                            'clear_cart'
5149                    });
5150                this.handleCartResponse(
5151                    result
5152                );
5153                this.dispatchCartEvent(
5154                    'loxshop:cart-cleared',
5155                    result
5156                );
5157                return result;
5158            } catch (error) {
5159                console.error(
5160                    'LOX SHOP CLEAR CART ERROR:',
5161                    error
5162                );
5163                this.showError(
5164                    error.message ||
5165                    'خطا در خالی کردن سبد خرید.'
5166                );
5167                return null;
5168            }
5169        },
5170        /*
5171        |--------------------------------------------------------------------------
5172        | HANDLE CART RESPONSE
5173        |--------------------------------------------------------------------------
5174        */
5175        handleCartResponse: function (result) {
5176            if (
5177                !result ||
5178                !result.cart
5179            ) {
5180                /*
5181                | در PHP cart ممکن است null باشد.
5182                */
5183                return;
5184            }
5185            const cart =
5186                result.cart;
5187            /*
5188            |--------------------------------------------------------------------------
5189            | ITEMS COUNT
5190            |--------------------------------------------------------------------------
5191            */
5192            document
5193                .querySelectorAll(
5194                    '[data-cart-items-count]'
5195                )
5196                .forEach(function (element) {
5197                    element.textContent =
5198                        Number(
5199                            result.items_count || 0
5200                        );
5201                });
5202            /*
5203            |--------------------------------------------------------------------------
5204            | TOTAL QUANTITY
5205            |--------------------------------------------------------------------------
5206            */
5207            document
5208                .querySelectorAll(
5209                    '[data-cart-quantity]'
5210                )
5211                .forEach(function (element) {
5212                    element.textContent =
5213                        Number(
5214                            result.quantity || 0
5215                        );
5216                });
5217            /*
5218            |--------------------------------------------------------------------------
5219            | SUBTOTAL
5220            |--------------------------------------------------------------------------
5221            */
5222            document
5223                .querySelectorAll(
5224                    '[data-cart-subtotal]'
5225                )
5226                .forEach(function (element) {
5227                    element.textContent =
5228                        this.formatMoney(
5229                            result.subtotal || 0
5230                        );
5231                }, this);
5232            /*
5233            |--------------------------------------------------------------------------
5234            | DISCOUNT
5235            |--------------------------------------------------------------------------
5236            */
5237            document
5238                .querySelectorAll(
5239                    '[data-cart-discount]'
5240                )
5241                .forEach(function (element) {
5242                    element.textContent =
5243                        this.formatMoney(
5244                     
5244       result.discount || 0
5245                        );
5246                }, this);
5247            /*
5248            |--------------------------------------------------------------------------
5249            | TOTAL
5250            |--------------------------------------------------------------------------
5251            */
5252            document
5253                .querySelectorAll(
5254                    '[data-cart-total]'
5255                )
5256                .forEach(function (element) {
5257                    element.textContent =
5258                        this.formatMoney(
5259                            result.total || 0
5260                        );
5261                }, this);
5262            /*
5263            |--------------------------------------------------------------------------
5264            | CART UUID
5265            |--------------------------------------------------------------------------
5266            */
5267            document
5268                .querySelectorAll(
5269                    '[data-cart-uuid]'
5270                )
5271                .forEach(function (element) {
5272                    element.textContent =
5273                        cart.uuid || '';
5274                    element.setAttribute(
5275                        'data-cart-uuid',
5276                        cart.uuid || ''
5277                    );
5278                });
5279            /*
5280            |--------------------------------------------------------------------------
5281            | CART ID
5282            |--------------------------------------------------------------------------
5283            */
5284            document
5285                .querySelectorAll(
5286                    '[data-cart-id]'
5287                )
5288                .forEach(function (element) {
5289                    element.setAttribute(
5290                        'data-cart-id',
5291                        cart.id || ''
5292                    );
5293                });
5294            /*
5295            |--------------------------------------------------------------------------
5296            | EMPTY CART STATE
5297            |--------------------------------------------------------------------------
5298            */
5299            document
5300                .querySelectorAll(
5301                    '[data-cart-empty]'
5302                )
5303                .forEach(function (element) {
5304                    element.hidden =
5305                        Number(
5306                            result.items_count || 0
5307                        ) > 0;
5308                });
5309            /*
5310            |--------------------------------------------------------------------------
5311            | HAS ITEMS STATE
5312            |--------------------------------------------------------------------------
5313            */
5314            document
5315                .querySelectorAll(
5316                    '[data-cart-has-items]'
5317                )
5318                .forEach(function (element) {
5319                    element.hidden =
5320                        Number(
5321                            result.items_count || 0
5322                        ) <= 0;
5323                });
5324            /*
5325            |--------------------------------------------------------------------------
5326            | CART ITEM COUNT BADGE
5327            |--------------------------------------------------------------------------
5328            */
5329            document
5330                .querySelectorAll(
5331                    '[data-cart-badge]'
5332                )
5333                .forEach(function (element) {
5334                    const count =
5335                        Number(
5336                            result.items_count || 0
5337                        );
5338                    element.textContent =
5339                        count;
5340                    element.hidden =
5341                        count <= 0;
5342                });
5343        },
5344        /*
5345        |--------------------------------------------------------------------------
5346        | FORMAT MONEY
5347        |--------------------------------------------------------------------------
5348        */
5349        formatMoney: function (value) {
5350            value =
5351                Number(value || 0);
5352            if (!isFinite(value)) {
5353                value = 0;
5354            }
5355            return value.toLocaleString(
5356                'en-US'
5357            );
5358        },
5359        /*
5360        |--------------------------------------------------------------------------
5361        | VALIDATE PRODUCT RESULT
5362        |--------------------------------------------------------------------------
5363        */
5364        getValidationData: function (result) {
5365            if (
5366                !result ||
5367                result.success !== true
5368            ) {
5369                return null;
5370            }
5371            return {
5372                product:
5373                    result.product || null,
5374                sku:
5375                    result.sku || null,
5376                price:
5377                    result.price || null,
5378                availability:
5379                    result.availability || null
5380            };
5381        },
5382        /*
5383        |--------------------------------------------------------------------------
5384        | DISPATCH EVENT
5385        |--------------------------------------------------------------------------
5386        */
5387        dispatchCartEvent: function (
5388            name,
5389            detail
5390        ) {
5391            try {
5392                document.dispatchEvent(
5393                    new CustomEvent(
5394                        name,
5395                        {
5396                            detail: detail
5397                        }
5398                    )
5399                );
5400            } catch (error) {
5401                /*
5402                | مرورگرهای خیلی قدیمی
5403                | در صورت نیاز می‌توان fallback اضافه کرد.
5404                */
5405            }
5406        },
5407        /*
5408        |--------------------------------------------------------------------------
5409        | SUCCESS
5410        |--------------------------------------------------------------------------
5411        */
5412
5413
5414        showSuccess: function (message) {
5415
5416            const element = document.getElementById(
5417                'shopresponse'
5418            );
5419
5420            if (!element) {
5421                return;
5422            }
5423
5424            element.className = 'messs msuccess';
5425
5426            element.innerHTML =
5427                message || 'عملیات با موفقیت انجام شد.';
5428
5429            element.hidden = false;
5430        },
5431
5432
5433
5434        showError: function (message) {
5435
5436            const element = document.getElementById(
5437                'shopresponse'
5438            );
5439
5440            if (!element) {
5441                return;
5442            }
5443
5444            element.className = 'messs merror';
5445            element.textContent =
5446                message || 'خطایی رخ داده است.';
5447
5448            element.hidden = false;
5449        }
5450
5451
5452    };
5453    /*
5454    |--------------------------------------------------------------------------
5455    | GLOBAL: ADD TO CART
5456    |--------------------------------------------------------------------------
5457    |
5458    | پشتیبانی:
5459    |
5460    | onclick="return add_to_cart(event)"
5461    |
5462    | onclick="return add_to_cart()"
5463    |
5464    |--------------------------------------------------------------------------
5465    */
5466    window.add_to_cart = function (event) {
5467        /*
5468        |--------------------------------------------------------------------------
5469        | EVENT
5470        |--------------------------------------------------------------------------
5471        */
5472        if (
5473            event &&
5474            typeof event.preventDefault === 'function'
5475        ) {
5476            event.preventDefault();
5477            return ShopCart.addToCart(
5478                event
5479            );
5480        }
5481        /*
5482        |--------------------------------------------------------------------------
5483        | ACTIVE ELEMENT
5484        |--------------------------------------------------------------------------
5485        */
5486        let element =
5487            document.activeElement;
5488        /*
5489        |--------------------------------------------------------------------------
5490        | اگر activeElement مناسب نبود
5491        |--------------------------------------------------------------------------
5492        */
5493        if (
5494            !element ||
5495            !element.closest
5496        ) {
5497            element =
5498                document.getElementById(
5499                    'add_btn'
5500                );
5501        }
5502        /*
5503        |--------------------------------------------------------------------------
5504        | ADD
5505        |--------------------------------------------------------------------------
5506        */
5507        ShopCart.addToCart(
5508            element
5509        );
5510        return false;
5511    };
5512    /*
5513    |--------------------------------------------------------------------------
5514    | GLOBAL: PRODUCT MINUS
5515    |--------------------------------------------------------------------------
5516    */
5517    window.product_minus = function (
5518        event,
5519        element
5520    ) {
5521        if (event) {
5522            event.preventDefault();
5523        }
5524        const form =
5525            ShopCart.getForm(element);
5526        if (!form) {
5527            return false;
5528        }
5529        const input =
5530            form.querySelector(
5531                'input[name="product_number"]'
5532            );
5533        if (!input) {
5534            return false;
5535        }
5536        let value =
5537            parseFloat(
5538                input.value
5539            );
5540        if (
5541            !isFinite(value) ||
5542            value < 1
5543        ) {
5544            value = 1;
5545        }
5546        value--;
5547        if (value < 1) {
5548            value = 1;
5549        }
5550        input.value = value;
5551        input.dispatchEvent(
5552            new Event(
5553                'change',
5554                {
5555                    bubbles: true
5556                }
5557            )
5558        );
5559        return false;
5560    };
5561    /*
5562    |--------------------------------------------------------------------------
5563    | GLOBAL: PRODUCT PLUS
5564    |--------------------------------------------------------------------------
5565    */
5566    window.product_plus = function (
5567        event,
5568        element
5569    ) {
5570        if (event) {
5571            event.preventDefault();
5572        }
5573        const form =
5574            ShopCart.getForm(element);
5575        if (!form) {
5576            return false;
5577        }
5578        const input =
5579            form.querySelector(
5580                'input[name="product_number"]'
5581            );
5582        if (!input) {
5583            return false;
5584        }
5585        let value =
5586            parseFloat(
5587                input.value
5588            );
5589        if (
5590            !isFinite(value) ||
5591            value < 1
5592        ) {
5593            value = 1;
5594        }
5595        value++;
5596        if (value > 999999) {
5597            value = 999999;
5598        }
5599        input.value = value;
5600        input.dispatchEvent(
5601            new Event(
5602                'change',
5603                {
5604                    bubbles: true
5605                }
5606            )
5607        );
5608        return false;
5609    };
5610    /*
5611    |--------------------------------------------------------------------------
5612    | FORM SUBMIT
5613    |--------------------------------------------------------------------------
5614    |
5615    | حتی بدون onclick:
5616    |
5617    | <form class="product-cart-form">
5618    |
5619    | AJAX اجرا می‌شود.
5620    |--------------------------------------------------------------------------
5621    */
5622    document.addEventListener(
5623        'submit',
5624        function (event) {
5625            const form =
5626                event.target.closest(
5627                    'form.product-cart-form, form[data-shop-cart-form]'
5628                );
5629            if (!form) {
5630                return;
5631            }
5632            /*
5633            | جلوگیری از submit معمولی
5634            */
5635            event.preventDefault();
5636            /*
5637            | ADD TO CART
5638            */
5639            ShopCart.addToCart(
5640                form
5641            );
5642        }
5643    );
5644    /*
5645    |--------------------------------------------------------------------------
5646    | GLOBAL EXPORT
5647    |--------------------------------------------------------------------------
5648    */
5649    window.LoxShopCart =
5650        ShopCart;
5651})(window, document);
5652
5653
5654
5655
5656
5657
5658
5659
5660
5661
5662
5663
5664
5665
5666
5667
5668
5669
5670
5671
5672
5673
5674
5675
5676(function () {
5677
5678    'use strict';
5679
5680    /*
5681    |--------------------------------------------------------------------------
5682    | PRODUCT SKU DATA
5683    |--------------------------------------------------------------------------
5684    */
5685
5686    const skuDataElement =
5687        document.getElementById('product-sku-data');
5688
5689    if (!skuDataElement) {
5690        return;
5691    }
5692
5693    let skuData = [];
5694
5695    try {
5696        skuData =
5697            JSON.parse(
5698                skuDataElement.textContent || '[]'
5699            );
5700    } catch (e) {
5701        console.error(
5702            'LoxShop: invalid SKU JSON',
5703            e
5704        );
5705
5706        return;
5707    }
5708
5709
5710    /*
5711    |--------------------------------------------------------------------------
5712    | ELEMENTS
5713    |--------------------------------------------------------------------------
5714    */
5715
5716    const form =
5717        skuDataElement.closest('form');
5718
5719    if (!form) {
5720        return;
5721    }
5722
5723
5724    /*
5725    |--------------------------------------------------------------------------
5726    | HELPERS
5727    |--------------------------------------------------------------------------
5728    */
5729
5730    function getVariationElements() {
5731
5732        return Array.from(
5733            form.querySelectorAll(
5734                '.product_var'
5735            )
5736        );
5737
5738    }
5739
5740
5741    /*
5742    |--------------------------------------------------------------------------
5743    | GET CURRENT SELECTIONS
5744    |--------------------------------------------------------------------------
5745    |
5746    | Result:
5747    |
5748    | {
5749    |     "1": "1",
5750    |     "9": "143"
5751    | }
5752    |
5753    |--------------------------------------------------------------------------
5754    */
5755
5756function getSelections(ignoreKey = null) {
5757
5758    const selections = {};
5759
5760    const elements = getVariationElements();
5761
5762    elements.forEach(function (element) {
5763
5764        console.log(
5765            'VAR ELEMENT:',
5766            {
5767                tag: element.tagName,
5768                key: element.getAttribute('key'),
5769                value: element.value,
5770                name: element.name,
5771                id: element.id,
5772                className: element.className
5773            }
5774        );
5775
5776        const key = element.getAttribute('key');
5777
5778        if (!key) {
5779            return;
5780        }
5781
5782        if (
5783            ignoreKey !== null &&
5784            String(key) === String(ignoreKey)
5785        ) {
5786            return;
5787        }
5788
5789        if (element.tagName === 'SELECT') {
5790
5791            const value = element.value;
5792
5793            console.log(
5794                'SELECT VALUE:',
5795                key,
5796                value
5797            );
5798
5799            if (
5800                value &&
5801                value !== '0'
5802            ) {
5803                selections[key] = String(value);
5804            }
5805
5806            return;
5807        }
5808
5809        if (element.type === 'radio') {
5810            if (element.checked) {
5811                selections[key] = String(element.value);
5812            }
5813            return;
5814        }
5815
5816        if (element.type === 'checkbox') {
5817            if (element.checked) {
5818                selections[key] = String(element.value);
5819            }
5820            return;
5821        }
5822
5823        if (
5824            element.value !== undefined &&
5825            element.value !== ''
5826        ) {
5827            selections[key] = String(element.value);
5828        }
5829
5830    });
5831
5832    console.log(
5833        'FINAL VARIATION SELECTIONS:',
5834        selections
5835    );
5836
5837    return selections;
5838}
5839
5840
5841    /*
5842    |--------------------------------------------------------------------------
5843    | CHECK SKU AGAINST SELECTIONS
5844    |--------------------------------------------------------------------------
5845    */
5846
5847    function skuMatches(
5848        sku,
5849        selections
5850    ) {
5851
5852        const attributes =
5853            sku.attributes || {};
5854
5855        for (
5856            const key in selections
5857        ) {
5858
5859            if (
5860                String(attributes[key]) !==
5861                String(selections[key])
5862            ) {
5863
5864                return false;
5865
5866            }
5867
5868        }
5869
5870        return true;
5871
5872    }
5873
5874
5875    /*
5876    |--------------------------------------------------------------------------
5877    | FIND EXACT SKU
5878    |--------------------------------------------------------------------------
5879    */
5880
5881    function findSelectedSku() {
5882
5883        const selections =
5884            getSelections();
5885
5886        const keys =
5887            Object.keys(selections);
5888
5889        /*
5890        |--------------------------------------------------------------------------
5891        | هنوز همه ویژگی‌ها انتخاب نشده‌اند
5892        |--------------------------------------------------------------------------
5893        */
5894
5895        const variationKeys =
5896            getVariationKeys();
5897
5898        if (
5899            keys.length !==
5900            variationKeys.length
5901        ) {
5902
5903            return null;
5904
5905        }
5906
5907
5908        return skuData.find(
5909            function (sku) {
5910
5911                return skuMatches(
5912                    sku,
5913                    selections
5914                );
5915
5916            }
5917        ) || null;
5918
5919    }
5920
5921
5922    /*
5923    |--------------------------------------------------------------------------
5924    | GET ALL ATTRIBUTE KEYS
5925    |--------------------------------------------------------------------------
5926    */
5927
5928    function getVariationKeys() {
5929
5930        const keys = [];
5931
5932        skuData.forEach(
5933            function (sku) {
5934
5935                const attributes =
5936                    sku.attributes || {};
5937
5938                Object.keys(attributes)
5939                    .forEach(
5940                        function (key) {
5941
5942                            if (
5943                                !keys.includes(key)
5944                            ) {
5945
5946                                keys.push(key);
5947
5948                            }
5949
5950                        }
5951                    );
5952
5953            }
5954        );
5955
5956        return keys;
5957
5958    }
5959
5960
5961    /*
5962    |--------------------------------------------------------------------------
5963    | FIND VALID VALUES FOR ATTRIBUTE
5964    |--------------------------------------------------------------------------
5965    |
5966    | مهم‌ترین قسمت سیستم
5967    |
5968    | برای هر attribute، انتخاب فعلی آن attribute
5969    | را نادیده می‌گیریم و بررسی می‌کنیم چه valueهایی
5970    | با بقیه انتخاب‌های کاربر سازگار هستند.
5971    |
5972    |--------------------------------------------------------------------------
5973    */
5974
5975    function getValidValuesForKey(
5976        key
5977    ) {
5978
5979        const selections =
5980            getSelections(key);
5981
5982        const validValues =
5983            new Set();
5984
5985
5986        skuData.forEach(
5987            function (sku) {
5988
5989                /*
5990                |--------------------------------------------------------------------------
5991                | SKU باید با انتخاب‌های سایر ویژگی‌ها
5992                | سازگار باشد.
5993                |--------------------------------------------------------------------------
5994                */
5995
5996                if (
5997                    !skuMatches(
5998                        sku,
5999                        selections
6000                    )
6001                ) {
6002
6003                    return;
6004
6005                }
6006
6007
6008                /*
6009                |--------------------------------------------------------------------------
6010                | فقط SKUهای قابل خرید
6011                |--------------------------------------------------------------------------
6012                */
6013
6014                if (
6015                    sku.is_purchasable === 0 ||
6016                    sku.is_purchasable === false
6017                ) {
6018
6019                    return;
6020
6021                }
6022
6023
6024                /*
6025                |--------------------------------------------------------------------------
6026                | موجودی
6027                |--------------------------------------------------------------------------
6028                */
6029
6030                if (
6031                    sku.available === false
6032                ) {
6033
6034                    return;
6035
6036                }
6037
6038
6039                if (
6040                    sku.attributes &&
6041                    sku.attributes[key] !== undefined
6042                ) {
6043
6044                    validValues.add(
6045                        String(
6046                            sku.attributes[key]
6047                        )
6048                    );
6049
6050                }
6051
6052            }
6053        );
6054
6055
6056        return validValues;
6057
6058    }
6059
6060
6061    /*
6062    |--------------------------------------------------------------------------
6063    | UPDATE SELECT OPTIONS
6064    |--------------------------------------------------------------------------
6065    */
6066
6067   function updateSelect(select) {
6068
6069    const key =
6070        select.getAttribute('key');
6071
6072    if (!key) {
6073        return;
6074    }
6075
6076    const validValues =
6077        getValidValuesForKey(key);
6078
6079    Array.from(
6080        select.options
6081    ).forEach(
6082        function (option) {
6083
6084            if (
6085                option.value === '0' ||
6086                option.value === ''
6087            ) {
6088                option.disabled = false;
6089                return;
6090            }
6091
6092            const valid =
6093                validValues.has(
6094                    String(option.value)
6095                );
6096
6097            option.disabled = !valid;
6098        }
6099    );
6100}
6101
6102
6103    /*
6104    |--------------------------------------------------------------------------
6105    | UPDATE RADIO / CHECKBOX
6106    |--------------------------------------------------------------------------
6107    */
6108
6109    function updateInput(
6110        element
6111    ) {
6112
6113        const key =
6114            element.getAttribute('key');
6115
6116        if (!key) {
6117            return;
6118        }
6119
6120
6121        const validValues =
6122            getValidValuesForKey(key);
6123
6124
6125        const valid =
6126            validValues.has(
6127                String(element.value)
6128            );
6129
6130
6131        element.disabled =
6132            !valid;
6133
6134
6135        /*
6136        |--------------------------------------------------------------------------
6137        | اگر انتخاب شده ولی دیگر معتبر نیست
6138        |--------------------------------------------------------------------------
6139        */
6140
6141        if (
6142            !valid &&
6143            element.checked
6144        ) {
6145
6146            element.checked = false;
6147
6148        }
6149
6150
6151        /*
6152        |--------------------------------------------------------------------------
6153        | برای label مربوط به input
6154        |--------------------------------------------------------------------------
6155        */
6156
6157        const label =
6158            element.closest('label');
6159
6160        if (label) {
6161
6162            label.classList.toggle(
6163                'loxshop-option-disabled',
6164                !valid
6165            );
6166
6167        }
6168
6169    }
6170
6171
6172    /*
6173    |--------------------------------------------------------------------------
6174    | UPDATE ALL OPTIONS
6175    |--------------------------------------------------------------------------
6176    */
6177
6178    function updateVariationOptions() {
6179
6180        getVariationElements()
6181            .forEach(
6182                function (element) {
6183
6184                    if (
6185                        element.tagName === 'SELECT'
6186                    ) {
6187
6188                        updateSelect(
6189                            element
6190                        );
6191
6192                        return;
6193
6194                    }
6195
6196
6197                    if (
6198                        element.type === 'radio' ||
6199                        element.type === 'checkbox'
6200                    ) {
6201
6202                        updateInput(
6203                            element
6204                        );
6205
6206                    }
6207
6208                }
6209            );
6210
6211    }
6212
6213
6214    function findLowestPricedSku() {
6215
6216
6217        const availableSkus =
6218            skuData.filter(function (sku) {
6219
6220                if (
6221                    sku.is_purchasable === 0 ||
6222                    sku.is_purchasable === false
6223                ) {
6224                    return false;
6225                }
6226
6227                if (
6228                    sku.available === false
6229                ) {
6230                    return false;
6231                }
6232
6233                if (
6234                    sku.final_price === null ||
6235                    sku.final_price === undefined ||
6236                    sku.final_price === ''
6237                ) {
6238                    return false;
6239                }
6240
6241                return true;
6242
6243            });
6244
6245
6246        if (!availableSkus.length) {
6247            return null;
6248        }
6249
6250
6251        return availableSkus.reduce(
6252            function (lowest, sku) {
6253
6254                return Number(sku.final_price) <
6255                    Number(lowest.final_price)
6256                    ? sku
6257                    : lowest;
6258
6259            }
6260        );
6261
6262
6263    }
6264    /*
6265    |--------------------------------------------------------------------------
6266    | UPDATE PRODUCT INFORMATION
6267    |--------------------------------------------------------------------------
6268    */
6269
6270    function updateProductInfo(sku) {
6271
6272        /*
6273        |--------------------------------------------------------------------------
6274        | هیچ تنوعی هنوز انتخاب نشده
6275        |--------------------------------------------------------------------------
6276        */
6277
6278        if (!sku) {
6279
6280            /*
6281            |--------------------------------------------------------------------------
6282            | اگر هیچ variation انتخاب نشده،
6283            | کمترین قیمت موجود را نمایش بده.
6284            |--------------------------------------------------------------------------
6285            */
6286
6287            const selections =
6288                getSelections();
6289
6290
6291            if (
6292                Object.keys(selections).length === 0
6293            ) {
6294
6295                const lowestSku =
6296                    findLowestPricedSku();
6297
6298
6299                if (lowestSku) {
6300
6301                    updateLoxShop(
6302                        'Product_Price',
6303                        formatPrice(
6304                            lowestSku.final_price
6305                        )
6306                    );
6307
6308
6309                    updateLoxShop(
6310                        'Product_Discount',
6311                        lowestSku.discount_percent > 0
6312                            ? formatPrice(lowestSku.price)
6313                            : ''
6314                    );
6315
6316
6317                    updateLoxShop(
6318                        'Product_Discount_Percent',
6319                        lowestSku.discount_percent || 0
6320                    );
6321
6322                }
6323
6324                return;
6325
6326            }
6327
6328
6329            /*
6330            |--------------------------------------------------------------------------
6331            | تعدادی variation انتخاب شده ولی هنوز SKU کامل نیست
6332            |--------------------------------------------------------------------------
6333            */
6334
6335            updateLoxShop(
6336                'Product_Price',
6337                ''
6338            );
6339
6340            updateLoxShop(
6341                'Product_Discount',
6342                ''
6343            );
6344
6345            updateLoxShop(
6346                'Product_Discount_Percent',
6347                '0'
6348            );
6349
6350            return;
6351
6352        }
6353
6354
6355        /*
6356        |--------------------------------------------------------------------------
6357        | SKU کامل انتخاب شده
6358        |--------------------------------------------------------------------------
6359        */
6360
6361        updateLoxShop(
6362            'Product_Price',
6363            formatPrice(
6364                sku.final_price
6365            )
6366        );
6367
6368
6369        updateLoxShop(
6370            'Product_Discount',
6371            sku.discount_percent > 0
6372                ? formatPrice(sku.price)
6373                : ''
6374        );
6375
6376
6377        updateLoxShop(
6378            'Product_Discount_Percent',
6379            sku.discount_percent || 0
6380        );
6381
6382
6383        updateStock(sku);
6384
6385        updateProductImage(sku);
6386
6387        updateCartButton(sku);
6388
6389
6390    }
6391
6392
6393
6394    /*
6395    |--------------------------------------------------------------------------
6396    | UPDATE LOXSHOP ELEMENTS
6397    |--------------------------------------------------------------------------
6398    */
6399
6400    function updateLoxShop(
6401        className,
6402        value
6403    ) {
6404
6405        const elements =
6406            document.querySelectorAll(
6407                '.LoxShop_' + className
6408            );
6409
6410
6411        elements.forEach(
6412            function (element) {
6413
6414                element.textContent =
6415                    value ?? '';
6416
6417            }
6418        );
6419
6420    }
6421
6422
6423    /*
6424    |--------------------------------------------------------------------------
6425    | FORMAT PRICE
6426    |--------------------------------------------------------------------------
6427    */
6428
6429    function formatPrice(
6430        value
6431    ) {
6432
6433        if (
6434            value === null ||
6435            value === undefined ||
6436            value === ''
6437        ) {
6438
6439            return '';
6440
6441        }
6442
6443
6444        return Number(value)
6445            .toLocaleString('en-US');
6446
6447    }
6448
6449
6450    /*
6451    |--------------------------------------------------------------------------
6452    | STOCK
6453    |--------------------------------------------------------------------------
6454    */
6455
6456    function updateStock(
6457        sku
6458    ) {
6459
6460        const elements =
6461            document.querySelectorAll(
6462                '.LoxShop_Product_Stock'
6463            );
6464
6465
6466        elements.forEach(
6467            function (element) {
6468
6469                if (
6470                    sku.manage_stock &&
6471                    Number(sku.stock_quantity) <= 0
6472                ) {
6473
6474                    element.textContent =
6475                        'ناموجود';
6476
6477                    return;
6478
6479                }
6480
6481
6482                if (
6483                    sku.manage_stock
6484                ) {
6485
6486                    element.textContent =
6487                        'موجودی: ' +
6488                        Number(
6489                            sku.stock_quantity
6490                        ).toLocaleString('en-US');
6491
6492                    return;
6493
6494                }
6495
6496
6497                element.textContent =
6498                    'موجود';
6499
6500            }
6501        );
6502
6503    }
6504
6505
6506    /*
6507    |--------------------------------------------------------------------------
6508    | PRODUCT IMAGE
6509    |--------------------------------------------------------------------------
6510    */
6511    function updateProductImage(sku) {
6512
6513        if (!sku || !sku.image) {
6514            return;
6515        }
6516
6517        var imageUrl = sku.image;
6518
6519        /*
6520         * ------------------------------------------------------------
6521         * 1. رفتار قبلی قالب را حفظ می‌کنیم
6522         * ------------------------------------------------------------
6523         */
6524
6525        const images = document.querySelectorAll(
6526            '.LoxShop_Product_Image'
6527        );
6528
6529        images.forEach(function (image) {
6530
6531            if (image.tagName === 'IMG') {
6532
6533                image.src = imageUrl;
6534
6535            } else {
6536
6537                image.style.backgroundImage =
6538                    'url("' + imageUrl + '")';
6539
6540            }
6541
6542        });
6543
6544
6545        /*
6546         * ------------------------------------------------------------
6547         * 2. اگر گالری حرفه‌ای وجود ندارد، تمام
6548         * ------------------------------------------------------------
6549         */
6550
6551        if (
6552            typeof window.lxProductGallery === 'undefined' ||
6553            !window.lxProductGallery
6554        ) {
6555            return;
6556        }
6557
6558
6559        /*
6560         * ------------------------------------------------------------
6561         * 3. نمایش تصویر SKU داخل گالری
6562         * ------------------------------------------------------------
6563         */
6564
6565        window.lxProductGallery.showImage(imageUrl);
6566
6567    }
6568
6569
6570    /*
6571    |--------------------------------------------------------------------------
6572    | ADD TO CART BUTTON
6573    |--------------------------------------------------------------------------
6574    */
6575
6576    function updateCartButton(
6577        sku
6578    ) {
6579
6580        const buttons =
6581            form.querySelectorAll(
6582                '#add_btn, [type="submit"]'
6583            );
6584
6585
6586        buttons.forEach(
6587            function (button) {
6588
6589                const purchasable =
6590                    sku &&
6591                    sku.is_purchasable &&
6592                    sku.available !== false;
6593
6594
6595                button.disabled =
6596                    !purchasable;
6597
6598
6599                if (!sku) {
6600
6601                    button.textContent =
6602                        'انتخاب تنوع';
6603
6604                    return;
6605
6606                }
6607
6608
6609                if (!purchasable) {
6610
6611                    button.textContent =
6612                        'ناموجود';
6613
6614                    return;
6615
6616                }
6617
6618
6619                button.textContent =
6620                    'افزودن به سبد';
6621
6622            }
6623        );
6624
6625    }
6626
6627
6628    /*
6629    |--------------------------------------------------------------------------
6630    | MAIN UPDATE
6631    |--------------------------------------------------------------------------
6632    */
6633
6634  function updateVariations() {
6635
6636    console.log(
6637        '========== UPDATE VARIATIONS =========='
6638    );
6639
6640    const selections =
6641        getSelections();
6642
6643    console.log(
6644        'SELECTIONS BEFORE OPTIONS:',
6645        selections
6646    );
6647
6648    updateVariationOptions();
6649
6650    const sku =
6651        findSelectedSku();
6652
6653    console.log(
6654        'MATCHED SKU:',
6655        sku
6656    );
6657
6658    updateProductInfo(
6659        sku
6660    );
6661
6662    form.dataset.selectedSkuId =
6663        sku
6664            ? sku.sku_id
6665            : '';
6666}
6667
6668    /*
6669    |--------------------------------------------------------------------------
6670    | EVENT HANDLER
6671    |--------------------------------------------------------------------------
6672    */
6673
6674    form.addEventListener(
6675        'change',
6676        function (event) {
6677
6678            if (
6679                !event.target.matches(
6680                    '.product_var'
6681                )
6682            ) {
6683
6684                return;
6685
6686            }
6687
6688
6689            updateVariations();
6690
6691        }
6692    );
6693
6694
6695    /*
6696    |--------------------------------------------------------------------------
6697    | INITIALIZE
6698    |--------------------------------------------------------------------------
6699    */
6700
6701    updateVariations();
6702
6703
6704    /*
6705    |--------------------------------------------------------------------------
6706    | PUBLIC API
6707    |--------------------------------------------------------------------------
6708    |
6709    | در صورت نیاز از بیرون هم قابل استفاده است.
6710    |--------------------------------------------------------------------------
6711    */
6712
6713    window.LoxShopProductVariations = {
6714
6715        getSku: function () {
6716
6717            return findSelectedSku();
6718
6719        },
6720
6721        getSelections: function () {
6722
6723            return getSelections();
6724
6725        },
6726
6727        update: function () {
6728
6729            updateVariations();
6730
6731        }
6732
6733    };
6734
6735})();
6736
6737
6738
6739
6740
6741
6742
6743
6744
6745
6746
6747
6748
6749
6750
6751(function () {
6752
6753    'use strict';
6754
6755
6756    /* ============================================================
6757     * LOXSHOP CART
6758     *
6759     * ساختار HTML:
6760     *
6761     * <tr data-cart-item-id="64">
6762     *
6763     * <input
6764     *     class="product_number"
6765     *     name="product[13][3]"
6766     *     value="6"
6767     * >
6768     *
6769     * ============================================================ */
6770
6771
6772    /* ============================================================
6773     * GET CART ROW
6774     * ============================================================ */
6775
6776    function getCartRow(element) {
6777
6778        if (!element) {
6779            return null;
6780        }
6781
6782        return element.closest(
6783            '#LOXSHOPCART tr[data-cart-item-id]'
6784        );
6785    }
6786
6787
6788    /* ============================================================
6789     * GET CART ITEM ID
6790     * ============================================================ */
6791
6792    function getCartItemId(element) {
6793
6794        const row = getCartRow(element);
6795
6796        if (!row) {
6797            return 0;
6798        }
6799
6800        return parseInt(
6801            row.getAttribute('data-cart-item-id') || '0',
6802            10
6803        );
6804    }
6805
6806
6807
6808
6809
6810    /* ============================================================
6811     * NORMALIZE NUMBER
6812     * ============================================================ */
6813
6814    function normalizeNumber(value) {
6815
6816        if (
6817            value === null ||
6818            value === undefined
6819        ) {
6820            return 0;
6821        }
6822
6823        value = String(value);
6824
6825        /* حذف جداکننده هزارگان */
6826        value = value.replace(/,/g, '');
6827
6828        /* تبدیل اعداد فارسی */
6829        value = value.replace(/[Û°-Û¹]/g, function (char) {
6830
6831            return String(
6832                'Û°Û±Û²Û³Û´ÛµÛ¶Û·Û¸Û¹'.indexOf(char)
6833            );
6834
6835        });
6836
6837        /* تبدیل اعداد عربی */
6838        value = value.replace(/[Ù -Ù©]/g, function (char) {
6839
6840            return String(
6841                '٠١٢٣٤٥٦٧٨٩'.indexOf(char)
6842            );
6843
6844        });
6845
6846        /* فقط عدد */
6847        value = value.replace(/[^\d]/g, '');
6848
6849        return parseInt(
6850            value || '0',
6851            10
6852        );
6853    }
6854
6855
6856    /* ============================================================
6857     * FORMAT NUMBER
6858     * ============================================================ */
6859
6860    function formatNumber(value) {
6861
6862        value = normalizeNumber(value);
6863
6864        return value.toLocaleString('en-US');
6865    }
6866
6867
6868
6869
6870
6871
6872    /* ============================================================
6873     * UPDATE CART TOTAL
6874     * ============================================================ */
6875
6876
6877
6878    window.updateCartTotal = function (cartResponse) {
6879
6880        if (!cartResponse) {
6881            return;
6882        }
6883
6884
6885        /* ========================================================
6886         * CART DATA
6887         * ======================================================== */
6888
6889        const cart = cartResponse.cart;
6890
6891        if (!cart) {
6892            console.warn(
6893                'Cart data not found.'
6894            );
6895            return;
6896        }
6897
6898
6899        /* ========================================================
6900         * TOTAL QUANTITY
6901         * ======================================================== */
6902
6903        document
6904            .querySelectorAll(
6905                '.LoxShop_Cart_Total_Count'
6906            )
6907            .forEach(function (element) {
6908
6909                element.textContent =
6910                    formatNumber(
6911                        cart.quantity || 0
6912                    ) + ' محصول';
6913
6914            });
6915
6916
6917        /* ========================================================
6918         * SUBTOTAL
6919         * ======================================================== */
6920
6921        document
6922            .querySelectorAll(
6923                '.LoxShop_Cart_Total_Price'
6924            )
6925            .forEach(function (element) {
6926
6927                element.textContent =
6928                    formatNumber(
6929                        cart.subtotal || 0
6930                    ) + ' تومان';
6931
6932            });
6933
6934
6935        /* ========================================================
6936         * DISCOUNT
6937         * ======================================================== */
6938
6939        document
6940            .querySelectorAll(
6941                '.LoxShop_Cart_Total_Discount_Profit'
6942            )
6943            .forEach(function (element) {
6944
6945                element.textContent =
6946                    formatNumber(
6947                        cart.discount || 0
6948                    ) + ' تومان';
6949
6950            });
6951
6952
6953        /* ========================================================
6954         * FINAL TOTAL
6955         * ======================================================== */
6956
6957        document
6958            .querySelectorAll(
6959                '.LoxShop_Cart_Total_All_Price'
6960            )
6961            .forEach(function (element) {
6962
6963                element.textContent =
6964                    formatNumber(
6965                        cart.total || 0
6966                    ) + ' تومان';
6967
6968            });
6969
6970    };
6971
6972
6973
6974    /* ============================================================
6975     * UPDATE CART ROW
6976     * ============================================================ */
6977
6978function updateCartRow(cartItem) {
6979    if (!cartItem) {
6980        return;
6981    }
6982
6983    /*
6984     * ============================================================
6985     * پیدا کردن ردیف مربوط به همین cart item
6986     * ============================================================
6987     */
6988
6989    const row =
6990        document.querySelector(
6991            '#LOXSHOPCART tr[data-cart-item-id="' +
6992            cartItem.cart_item_id +
6993            '"]'
6994        );
6995
6996    if (!row) {
6997        console.warn(
6998            'Cart row not found:',
6999            cartItem.cart_item_id
7000        );
7001        return;
7002    }
7003
7004
7005    /*
7006     * ============================================================
7007     * QUANTITY
7008     * ============================================================
7009     */
7010
7011    const input =
7012        row.querySelector(
7013            'input.product_number'
7014        );
7015
7016    if (input) {
7017        setQuantity(
7018            input,
7019            cartItem.quantity
7020        );
7021    }
7022
7023
7024    /*
7025     * ============================================================
7026     * LINE TOTAL
7027     * ============================================================
7028     *
7029     * فقط قیمت همین ردیف را تغییر می‌دهیم.
7030     *
7031     * ============================================================
7032     */
7033
7034    const totalElement =
7035        row.querySelector(
7036            '.LoxShop_Cart_Item_Total_Price'
7037        );
7038
7039    if (totalElement) {
7040
7041        const lineTotal =
7042            normalizeNumber(
7043                cartItem.line_total || 0
7044            );
7045
7046        totalElement.textContent =
7047            formatNumber(
7048                lineTotal
7049            );
7050    }
7051}
7052
7053
7054
7055
7056
7057
7058
7059    /* ============================================================
7060     * SET QUANTITY
7061     * ============================================================ */
7062
7063    function setQuantity(input, value) {
7064
7065        if (!input) {
7066            return;
7067        }
7068
7069        value = normalizeNumber(value);
7070
7071        if (value < 1) {
7072            value = 1;
7073        }
7074
7075        input.value = formatNumber(value);
7076    }
7077
7078
7079
7080
7081
7082
7083    /* ============================================================
7084 * CART QUANTITY AUTO UPDATE
7085 *
7086 * بعد از آخرین تغییر تعداد، 2 ثانیه صبر می‌کنیم
7087 * و سپس quantity را در checkorder.php ذخیره می‌کنیم.
7088 * ============================================================ */
7089
7090    const cartQuantityTimers = new WeakMap();
7091
7092    function scheduleCartQuantityUpdate(input) {
7093
7094        if (!input) {
7095            return;
7096        }
7097
7098        const row = input.closest(
7099            '#LOXSHOPCART tr[data-cart-item-id]'
7100        );
7101
7102        if (!row) {
7103            return;
7104        }
7105
7106        const cartItemId = parseInt(
7107            row.getAttribute('data-cart-item-id') || '0',
7108            10
7109        );
7110
7111        if (!cartItemId) {
7112            console.error(
7113                'Invalid cart item ID for quantity update.'
7114            );
7115            return;
7116        }
7117
7118        const quantity = normalizeNumber(input.value);
7119
7120        if (quantity < 1) {
7121            return;
7122        }
7123
7124        /* --------------------------------------------------------
7125         * اگر تایمر قبلی وجود دارد، لغوش می‌کنیم
7126         * -------------------------------------------------------- */
7127
7128        const oldTimer = cartQuantityTimers.get(input);
7129
7130        if (oldTimer) {
7131            clearTimeout(oldTimer);
7132        }
7133
7134        /* --------------------------------------------------------
7135         * نمایش وضعیت در حال ذخیره
7136         * -------------------------------------------------------- */
7137
7138        input.dataset.saving = '1';
7139
7140        /* --------------------------------------------------------
7141         * DEBOUNCE = 2 ثانیه
7142         * -------------------------------------------------------- */
7143
7144        const timer = setTimeout(function () {
7145
7146            updateCartQuantity(
7147                input,
7148                row,
7149                cartItemId,
7150                quantity
7151            );
7152
7153        }, 2000);
7154
7155        cartQuantityTimers.set(input, timer);
7156    }
7157
7158
7159    /* ============================================================
7160     * UPDATE CART QUANTITY
7161     * ============================================================ */
7162
7163
7164    function updateCartQuantity(
7165
7166        input,
7167
7168        row,
7169
7170        cartItemId,
7171
7172        quantity
7173
7174    ) {
7175
7176        if (!input || !row) {
7177
7178            return;
7179        }
7180
7181
7182        /* --------------------------------------------------------
7183         * جلوگیری از ارسال همزمان
7184         * -------------------------------------------------------- */
7185
7186        if (input.dataset.updating === '1') {
7187
7188            return;
7189        }
7190
7191        input.dataset.updating = '1';
7192
7193
7194        /* --------------------------------------------------------
7195         * ذخیره مقدار قبلی برای rollback در صورت خطا
7196         * -------------------------------------------------------- */
7197
7198        const previousQuantity =
7199
7200            input.dataset.previousQuantity !== undefined
7201
7202                ? normalizeNumber(
7203                    input.dataset.previousQuantity
7204                )
7205
7206                : quantity;
7207
7208
7209        /* --------------------------------------------------------
7210         * REQUEST DATA
7211         * -------------------------------------------------------- */
7212
7213        const formData = new FormData();
7214
7215        formData.append(
7216            'action',
7217            'update_cart'
7218        );
7219
7220        formData.append(
7221            'cart_item_id',
7222            String(cartItemId)
7223        );
7224
7225        formData.append(
7226            'quantity',
7227            String(quantity)
7228        );
7229
7230
7231        /* --------------------------------------------------------
7232         * ارسال به checkorder.php
7233         * -------------------------------------------------------- */
7234
7235        fetch(
7236
7237            '/shoporder/checkorder.php',
7238
7239            {
7240
7241                method: 'POST',
7242
7243                body: formData,
7244
7245                credentials: 'same-origin',
7246
7247                headers: {
7248
7249                    'X-Requested-With':
7250                        'XMLHttpRequest',
7251
7252                    'Accept':
7253                        'application/json'
7254                }
7255            }
7256        )
7257
7258
7259            /* ====================================================
7260             * RESPONSE
7261             * ==================================================== */
7262
7263            .then(function (response) {
7264
7265                /*
7266                 * حتی در HTTP 400 نیز JSON را می‌خوانیم.
7267                 *
7268                 * checkorder.php ممکن است مثلاً این پاسخ را بدهد:
7269                 *
7270                 * {
7271                 *     success: false,
7272                 *     error: "out_of_stock",
7273                 *     message: "موجودی این کالا کافی نیست."
7274                 * }
7275                 */
7276
7277                return response.json()
7278
7279                    .then(function (result) {
7280
7281                        return {
7282
7283                            ok:
7284                                response.ok,
7285
7286                            status:
7287                                response.status,
7288
7289                            result:
7290                                result
7291                        };
7292                    })
7293
7294                    .catch(function () {
7295
7296                        /*
7297                         * اگر پاسخ سرور JSON معتبر نبود
7298                         */
7299                        return {
7300
7301                            ok:
7302                                response.ok,
7303
7304                            status:
7305                                response.status,
7306
7307                            result:
7308                                null
7309                        };
7310                    });
7311            })
7312
7313
7314            /* ====================================================
7315             * RESULT
7316             * ==================================================== */
7317
7318            .then(function (responseData) {
7319
7320                var result =
7321                    responseData.result;
7322
7323
7324                console.log(
7325                    'Cart quantity update response:',
7326                    responseData
7327                );
7328
7329
7330                /* ------------------------------------------------
7331                 * HTTP ERROR یا خطای منطقی
7332                 * ------------------------------------------------ */
7333
7334                if (
7335
7336                    !responseData.ok ||
7337
7338                    !result ||
7339
7340                    result.success !== true
7341
7342                ) {
7343
7344                    /*
7345                     * برگرداندن مقدار قبلی
7346                     */
7347                    if (previousQuantity > 0) {
7348
7349                        input.value =
7350                            formatNumber(
7351                                previousQuantity
7352                            );
7353                    }
7354
7355
7356                    /*
7357                     * ذخیره دوباره مقدار قبلی
7358                     */
7359                    input.dataset.previousQuantity =
7360                        String(
7361                            previousQuantity
7362                        );
7363
7364
7365                    input.dataset.saving =
7366                        '0';
7367
7368
7369                    /*
7370                     * نمایش پیام واقعی سرور
7371                     */
7372                    alert(
7373
7374                        result && result.message
7375
7376                            ? result.message
7377
7378                            : 'تعداد محصول به‌روزرسانی نشد.'
7379                    );
7380
7381
7382                    return;
7383                }
7384
7385
7386                /* ------------------------------------------------
7387                 * SUCCESS
7388                 * ------------------------------------------------ */
7389
7390                input.dataset.previousQuantity =
7391                    String(
7392                        quantity
7393                    );
7394
7395                input.dataset.saving =
7396                    '0';
7397
7398
7399                console.log(
7400                    'Cart quantity updated:',
7401                    cartItemId,
7402                    quantity
7403                );
7404
7405
7406                /* ------------------------------------------------
7407                 * UPDATE CART ROW
7408                 * ------------------------------------------------ */
7409
7410                if (
7411
7412                    result.cart &&
7413
7414                    Array.isArray(
7415                        result.cart.items
7416                    )
7417
7418                ) {
7419
7420                    const updatedItem =
7421
7422                        result.cart.items.find(
7423
7424                            function (item) {
7425
7426                                return String(
7427                                    item.cart_item_id
7428                                ) === String(
7429                                    cartItemId
7430                                );
7431                            }
7432                        );
7433
7434
7435                    if (updatedItem) {
7436
7437                        updateCartRow(
7438                            updatedItem
7439                        );
7440                    }
7441                }
7442
7443
7444                /* ------------------------------------------------
7445                 * UPDATE CART TOTAL
7446                 * ------------------------------------------------ */
7447
7448                updateCartTotal(
7449                    result
7450                );
7451            })
7452
7453
7454            /* ====================================================
7455             * ERROR
7456             * ==================================================== */
7457
7458            .catch(function (error) {
7459
7460                console.error(
7461                    'Cart quantity update error:',
7462                    error
7463                );
7464
7465
7466                input.dataset.saving =
7467                    '0';
7468
7469
7470                /*
7471                 * در صورت خطای واقعی شبکه،
7472                 * HTTP یا JavaScript
7473                 *
7474                 * مقدار قبلی را برمی‌گردانیم.
7475                 */
7476                if (previousQuantity > 0) {
7477
7478                    input.value =
7479                        formatNumber(
7480                            previousQuantity
7481                        );
7482                }
7483
7484
7485                input.dataset.previousQuantity =
7486                    String(
7487                        previousQuantity
7488                    );
7489
7490
7491                alert(
7492
7493                    error.message ||
7494
7495                    'خطا در ارتباط با سرور. لطفاً دوباره تلاش کنید.'
7496                );
7497            })
7498
7499
7500            /* ====================================================
7501             * FINALLY
7502             * ==================================================== */
7503
7504            .finally(function () {
7505
7506                input.dataset.updating =
7507                    '0';
7508            });
7509    }
7510
7511
7512
7513
7514
7515
7516
7517
7518
7519    /* ============================================================
7520     * GET QUANTITY INPUT
7521     *
7522     * پشتیبانی از:
7523     *
7524     * 1. Cart
7525     *    input.product_number
7526     *
7527     * 2. Product page
7528     *    input[name="product_number"]
7529     * ============================================================ */
7530
7531    function getQuantityInput(element) {
7532
7533        if (!element) {
7534            return null;
7535        }
7536
7537
7538        /* ========================================================
7539         * CART ROW
7540         * ======================================================== */
7541
7542        const row =
7543            element.closest(
7544                '#LOXSHOPCART tr[data-cart-item-id]'
7545            );
7546
7547        if (row) {
7548
7549            const cartInput =
7550                row.querySelector(
7551                    'input.product_number, input[name="product_number"]'
7552                );
7553
7554            if (cartInput) {
7555                return cartInput;
7556            }
7557        }
7558
7559
7560        /* ========================================================
7561         * PRODUCT CART FORM
7562         * ======================================================== */
7563
7564        const productForm =
7565            element.closest(
7566                'form.product-cart-form'
7567            );
7568
7569        if (productForm) {
7570
7571            const productInput =
7572                productForm.querySelector(
7573                    'input[name="product_number"]'
7574                );
7575
7576            if (productInput) {
7577                return productInput;
7578            }
7579        }
7580
7581
7582        /* ========================================================
7583         * FALLBACK
7584         * ======================================================== */
7585
7586        const parent =
7587            element.parentElement;
7588
7589        if (parent) {
7590
7591            const input =
7592                parent.querySelector(
7593                    'input.product_number, input[name="product_number"]'
7594                );
7595
7596            if (input) {
7597                return input;
7598            }
7599        }
7600
7601
7602        return null;
7603    }
7604
7605
7606    /* ============================================================
7607     * PLUS
7608     * ============================================================ */
7609
7610    window.product_plus = function (event, button) {
7611
7612        if (event) {
7613            event.preventDefault();
7614            event.stopPropagation();
7615        }
7616
7617        const input =
7618            getQuantityInput(button);
7619
7620        if (!input) {
7621
7622            console.error(
7623                'Quantity input not found.'
7624            );
7625
7626            return false;
7627        }
7628
7629        /*
7630         * مقدار فعلی را قبل از تغییر نگه می‌داریم
7631         */
7632        input.dataset.previousQuantity =
7633            String(
7634                normalizeNumber(input.value)
7635            );
7636
7637        let quantity =
7638            normalizeNumber(input.value);
7639
7640        quantity++;
7641
7642        setQuantity(
7643            input,
7644            quantity
7645        );
7646
7647        /*
7648         * ذخیره خودکار با debounce دو ثانیه
7649         */
7650        scheduleCartQuantityUpdate(input);
7651
7652        return false;
7653    };
7654
7655
7656    /* ============================================================
7657     * MINUS
7658     * ============================================================ */
7659
7660    window.product_minus = function (event, button) {
7661
7662        if (event) {
7663            event.preventDefault();
7664            event.stopPropagation();
7665        }
7666
7667        const input =
7668            getQuantityInput(button);
7669
7670        if (!input) {
7671
7672            console.error(
7673                'Quantity input not found.'
7674            );
7675
7676            return false;
7677        }
7678
7679        /*
7680         * مقدار فعلی را قبل از تغییر نگه می‌داریم
7681         */
7682        input.dataset.previousQuantity =
7683            String(
7684                normalizeNumber(input.value)
7685            );
7686
7687        let quantity =
7688            normalizeNumber(input.value);
7689
7690        quantity--;
7691
7692        if (quantity < 1) {
7693            quantity = 1;
7694        }
7695
7696        setQuantity(
7697            input,
7698            quantity
7699        );
7700
7701        /*
7702         * ذخیره خودکار با debounce دو ثانیه
7703         */
7704        scheduleCartQuantityUpdate(input);
7705
7706        return false;
7707    };
7708
7709
7710
7711
7712
7713
7714
7715
7716
7717    /* ============================================================
7718     * QUANTITY INPUT
7719     * ============================================================ */
7720    document.addEventListener(
7721        'input',
7722        function (event) {
7723
7724            const input =
7725                event.target.closest(
7726                    '#LOXSHOPCART input.product_number, ' +
7727                    'form.product-cart-form input[name="product_number"]'
7728                );
7729
7730            if (!input) {
7731                return;
7732            }
7733
7734
7735            let value =
7736                normalizeNumber(
7737                    input.value
7738                );
7739
7740
7741            if (value < 1) {
7742                value = 1;
7743            }
7744
7745
7746            input.value =
7747                formatNumber(value);
7748
7749
7750            if (
7751                input.closest(
7752                    '#LOXSHOPCART tr[data-cart-item-id]'
7753                )
7754            ) {
7755
7756                scheduleCartQuantityUpdate(input);
7757
7758            }
7759
7760
7761
7762
7763
7764        }
7765    );
7766
7767
7768    document.addEventListener(
7769        'blur',
7770        function (event) {
7771
7772            /*
7773             * اطمینان از اینکه target یک Element معتبر است
7774             * و متد closest را دارد.
7775             */
7776            if (
7777                !event.target ||
7778                typeof event.target.closest !== 'function'
7779            ) {
7780                return;
7781            }
7782
7783
7784            /*
7785             * پیدا کردن input تعداد محصول
7786             */
7787            const input =
7788                event.target.closest(
7789                    '#LOXSHOPCART input.product_number, ' +
7790                    'form.product-cart-form input[name="product_number"]'
7791                );
7792
7793
7794            /*
7795             * اگر input مربوط به تعداد محصول نبود
7796             */
7797            if (!input) {
7798                return;
7799            }
7800
7801
7802            /*
7803             * تبدیل مقدار به عدد استاندارد
7804             */
7805            let value =
7806                normalizeNumber(
7807                    input.value
7808                );
7809
7810
7811            /*
7812             * حداقل تعداد = 1
7813             */
7814            if (value < 1) {
7815                value = 1;
7816            }
7817
7818
7819            /*
7820             * فرمت کردن مجدد مقدار
7821             */
7822            input.value =
7823                formatNumber(value);
7824
7825        },
7826        true
7827    );
7828
7829
7830
7831
7832
7833
7834
7835
7836
7837    /* ============================================================
7838     * REMOVE CART ITEM
7839     *
7840     * HTML:
7841     *
7842     * onclick="removeCartItem(this)"
7843     *
7844     * ID از TR گرفته می‌شود:
7845     *
7846     * <tr data-cart-item-id="64">
7847     *
7848     * ============================================================ */
7849
7850    window.removeCartItem = function (button) {
7851
7852        if (!button) {
7853            return;
7854        }
7855
7856        const cartItemId =
7857            getCartItemId(button);
7858
7859        if (!cartItemId) {
7860            console.error(
7861                'Invalid cart item ID.'
7862            );
7863            return;
7864        }
7865
7866        update_basket(
7867            cartItemId,
7868            'del',
7869            'cart'
7870        );
7871    };
7872
7873
7874    /* ============================================================
7875     * PREPARE CART FORM
7876     *
7877     * قبل از ارسال:
7878     *
7879     * 1,000
7880     *
7881     * تبدیل می‌شود به:
7882     *
7883     * 1000
7884     *
7885     * ============================================================ */
7886
7887    function prepareCartForm(form) {
7888
7889        if (!form) {
7890            return;
7891        }
7892
7893        const inputs =
7894            form.querySelectorAll(
7895                'input.product_number'
7896            );
7897
7898        inputs.forEach(function (input) {
7899
7900            input.value =
7901                normalizeNumber(
7902                    input.value
7903                );
7904
7905        });
7906    }
7907
7908
7909    /* ============================================================
7910     * CART FORM SUBMIT
7911     * ============================================================ */
7912
7913    document.addEventListener(
7914        'submit',
7915        function (event) {
7916
7917            const form =
7918                event.target.closest(
7919                    '#form_cart'
7920                );
7921
7922            if (!form) {
7923                return;
7924            }
7925
7926            prepareCartForm(form);
7927
7928        }
7929    );
7930
7931
7932    /* ============================================================
7933     * DEBUG HELPER
7934     * ============================================================ */
7935
7936    window.LoxShopCart = {
7937
7938        getRow: function (element) {
7939
7940            return getCartRow(element);
7941
7942        },
7943
7944        getItemId: function (element) {
7945
7946            return getCartItemId(element);
7947
7948        },
7949
7950        getQuantity: function (element) {
7951
7952            const input =
7953                getQuantityInput(element);
7954
7955            if (!input) {
7956                return 0;
7957            }
7958
7959            return normalizeNumber(
7960                input.value
7961            );
7962        },
7963
7964        setQuantity: function (
7965            element,
7966            value
7967        ) {
7968
7969            const input =
7970                getQuantityInput(element);
7971
7972            if (!input) {
7973                return;
7974            }
7975
7976            setQuantity(
7977                input,
7978                value
7979            );
7980        }
7981
7982    };
7983
7984
7985})();
7986
7987
7988
7989
7990
7991
7992
7993function update_basket(cartItemId, action, type) {
7994
7995    'use strict';
7996
7997    /* ============================================================
7998     * VALIDATE CART ITEM ID
7999     * ============================================================ */
8000
8001    cartItemId = parseInt(cartItemId, 10);
8002
8003    if (!cartItemId || cartItemId <= 0) {
8004
8005        console.error(
8006            'Invalid cart item ID:',
8007            cartItemId
8008        );
8009
8010        return false;
8011    }
8012
8013
8014    /* ============================================================
8015     * VALIDATE TYPE
8016     * ============================================================ */
8017
8018    if (type !== 'cart') {
8019
8020        console.error(
8021            'Invalid basket type:',
8022            type
8023        );
8024
8025        return false;
8026    }
8027
8028
8029    /* ============================================================
8030     * MAP OLD ACTION TO NEW CHECKORDER ACTION
8031     *
8032     * update_basket(..., 'del', 'cart')
8033     *
8034     * =>
8035     *
8036     * checkorder.php
8037     * action=remove_from_cart
8038     * ============================================================ */
8039
8040    let requestAction = '';
8041
8042    if (action === 'del') {
8043
8044        requestAction = 'remove_from_cart';
8045
8046    }
8047    else if (action === 'remove') {
8048
8049        requestAction = 'remove_from_cart';
8050
8051    }
8052    else {
8053
8054        console.error(
8055            'Invalid basket action:',
8056            action
8057        );
8058
8059        return false;
8060    }
8061
8062
8063    /* ============================================================
8064     * FIND CART ROW
8065     * ============================================================ */
8066
8067    const row = document.querySelector(
8068        '#LOXSHOPCART tr[data-cart-item-id="' +
8069        cartItemId +
8070        '"]'
8071    );
8072
8073    if (!row) {
8074
8075        console.error(
8076            'Cart row not found:',
8077            cartItemId
8078        );
8079
8080        return false;
8081    }
8082
8083
8084    /* ============================================================
8085     * CONFIRM
8086     * ============================================================ */
8087
8088    if (
8089        !confirm(
8090            'آیا از حذف این محصول از سبد خرید مطمئن هستید؟'
8091        )
8092    ) {
8093
8094        return false;
8095    }
8096
8097
8098    /* ============================================================
8099     * PREVENT DOUBLE CLICK
8100     * ============================================================ */
8101
8102    if (
8103        row.dataset.removing === '1'
8104    ) {
8105
8106        return false;
8107    }
8108
8109    row.dataset.removing = '1';
8110
8111
8112    /* ============================================================
8113     * LOADING
8114     * ============================================================ */
8115
8116    const oldOpacity =
8117        row.style.opacity;
8118
8119    const oldPointerEvents =
8120        row.style.pointerEvents;
8121
8122    row.style.opacity = '0.5';
8123    row.style.pointerEvents = 'none';
8124
8125
8126    /* ============================================================
8127     * REQUEST DATA
8128     * ============================================================ */
8129
8130    const formData = new FormData();
8131
8132    formData.append(
8133        'action',
8134        requestAction
8135    );
8136
8137    formData.append(
8138        'cart_item_id',
8139        String(cartItemId)
8140    );
8141
8142
8143    /* ============================================================
8144     * SEND REQUEST
8145     *
8146     * /shoporder/checkorder.php
8147     * ============================================================ */
8148
8149    fetch(
8150        '/shoporder/checkorder.php',
8151        {
8152            method: 'POST',
8153
8154            body: formData,
8155
8156            credentials: 'same-origin',
8157
8158            headers: {
8159                'X-Requested-With':
8160                    'XMLHttpRequest',
8161
8162                'Accept':
8163                    'application/json'
8164            }
8165        }
8166    )
8167
8168        /* ============================================================
8169         * RESPONSE
8170         * ============================================================ */
8171
8172        .then(function (response) {
8173
8174            if (!response.ok) {
8175
8176                throw new Error(
8177                    'HTTP error: ' +
8178                    response.status
8179                );
8180            }
8181
8182            return response.json();
8183        })
8184
8185
8186        /* ============================================================
8187         * JSON RESULT
8188         * ============================================================ */
8189
8190        .then(function (result) {
8191
8192            console.log(
8193                'Cart remove response:',
8194                result
8195            );
8196
8197
8198            /* ========================================================
8199             * PHP ERROR
8200             * ======================================================== */
8201
8202            if (
8203                !result ||
8204                result.success !== true
8205            ) {
8206
8207                throw new Error(
8208                    result &&
8209                        result.message
8210
8211                        ? result.message
8212
8213                        : 'حذف کالا انجام نشد.'
8214                );
8215            }
8216
8217
8218            /* ========================================================
8219             * REMOVE ROW FROM DOM
8220             * ======================================================== */
8221
8222            row.remove();
8223
8224
8225            /* ========================================================
8226             * OPTIONAL:
8227             * UPDATE CART INFORMATION
8228             *
8229             * اگر بعداً در پاسخ PHP اطلاعاتی مثل:
8230             *
8231             * cart.total
8232             * cart.items_count
8233             *
8234             * داشته باشیم، اینجا می‌توانیم
8235             * بخش‌های مربوط به سبد را آپدیت کنیم.
8236             * ======================================================== */
8237
8238            if (
8239                result.cart
8240            ) {
8241
8242                console.log(
8243                    'Updated cart:',
8244                    result.cart
8245                );
8246                updateCartTotal(result);
8247            }
8248
8249
8250            /* ========================================================
8251             * CART EMPTY
8252             * ======================================================== */
8253
8254            const cartTable =
8255                document.querySelector(
8256                    '#LOXSHOPCART'
8257                );
8258
8259            if (cartTable) {
8260
8261                const remainingRows =
8262                    cartTable.querySelectorAll(
8263                        'tr[data-cart-item-id]'
8264                    );
8265
8266                if (
8267                    remainingRows.length === 0
8268                ) {
8269
8270                    console.log(
8271                        'Cart is now empty.'
8272                    );
8273                }
8274            }
8275
8276        })
8277
8278
8279        /* ============================================================
8280         * ERROR
8281         * ============================================================ */
8282
8283        .catch(function (error) {
8284
8285            console.error(
8286                'Cart remove error:',
8287                error
8288            );
8289
8290
8291            /* ========================================================
8292             * RESTORE ROW
8293             * ======================================================== */
8294
8295            row.dataset.removing = '0';
8296
8297            row.style.opacity =
8298                oldOpacity;
8299
8300            row.style.pointerEvents =
8301                oldPointerEvents;
8302
8303
8304            /* ========================================================
8305             * SHOW ERROR
8306             * ======================================================== */
8307
8308            alert(
8309                error.message ||
8310                'خطا در حذف محصول از سبد خرید.'
8311            );
8312        });
8313
8314
8315    return false;
8316}
8317
8318
8319
8320/* ============================================================
8321 * LOXSHOP - CLEAR CART
8322 * ============================================================ */
8323
8324(function () {
8325
8326    'use strict';
8327
8328
8329    /* ============================================================
8330     * CLEAR CART
8331     * ============================================================ */
8332
8333    function clearCart() {
8334
8335        /* --------------------------------------------------------
8336         * CONFIRM
8337         * -------------------------------------------------------- */
8338
8339        if (
8340            !confirm(
8341                'مطمئنید می‌خواهید سبد خرید را خالی کنید؟'
8342            )
8343        ) {
8344            return false;
8345        }
8346
8347
8348        /* --------------------------------------------------------
8349         * CART
8350         * -------------------------------------------------------- */
8351
8352        const cart =
8353            document.querySelector(
8354                '#LOXSHOPCART'
8355            );
8356
8357        if (!cart) {
8358
8359            console.error(
8360                'LOXSHOPCART not found.'
8361            );
8362
8363            return false;
8364        }
8365
8366
8367        /* --------------------------------------------------------
8368         * FORM
8369         * -------------------------------------------------------- */
8370
8371        const form =
8372            cart.querySelector(
8373                '#form_cart'
8374            );
8375
8376        if (!form) {
8377
8378            console.error(
8379                'Cart form not found.'
8380            );
8381
8382            return false;
8383        }
8384
8385
8386        /* --------------------------------------------------------
8387         * BUTTON
8388         * -------------------------------------------------------- */
8389
8390        const button =
8391            form.querySelector(
8392                'button[name="command"][value="clear"]'
8393            );
8394
8395
8396        /* --------------------------------------------------------
8397         * LOADING
8398         * -------------------------------------------------------- */
8399
8400        if (button) {
8401
8402            if (
8403                button.dataset.loading === '1'
8404            ) {
8405                return false;
8406            }
8407
8408            button.dataset.loading = '1';
8409
8410            button.disabled = true;
8411
8412            button.dataset.originalText =
8413                button.innerHTML;
8414
8415            button.innerHTML =
8416                'در حال خالی کردن...';
8417        }
8418
8419
8420        /* --------------------------------------------------------
8421         * REQUEST
8422         * -------------------------------------------------------- */
8423
8424        const formData =
8425            new FormData();
8426
8427
8428        formData.append(
8429            'action',
8430            'clear_cart'
8431        );
8432
8433
8434        /* --------------------------------------------------------
8435         * SEND REQUEST
8436         * -------------------------------------------------------- */
8437
8438        fetch(
8439            '/shoporder/checkorder.php',
8440            {
8441                method: 'POST',
8442
8443                body: formData,
8444
8445                credentials: 'same-origin',
8446
8447                headers: {
8448                    'X-Requested-With':
8449                        'XMLHttpRequest',
8450
8451                    'Accept':
8452                        'application/json'
8453                }
8454            }
8455        )
8456
8457            /* --------------------------------------------------------
8458             * RESPONSE
8459             * -------------------------------------------------------- */
8460
8461            .then(function (response) {
8462
8463                if (!response.ok) {
8464
8465                    throw new Error(
8466                        'HTTP error: ' +
8467                        response.status
8468                    );
8469                }
8470
8471                return response.json();
8472            })
8473
8474
8475            /* --------------------------------------------------------
8476             * RESULT
8477             * -------------------------------------------------------- */
8478
8479            .then(function (result) {
8480
8481                console.log(
8482                    'Clear cart response:',
8483                    result
8484                );
8485
8486
8487                /* ----------------------------------------------------
8488                 * PHP ERROR
8489                 * ---------------------------------------------------- */
8490
8491                if (
8492                    !result ||
8493                    result.success !== true
8494                ) {
8495
8496                    throw new Error(
8497                        result &&
8498                            result.message
8499
8500                            ? result.message
8501
8502                            : 'خالی کردن سبد خرید انجام نشد.'
8503                    );
8504                }
8505
8506
8507
8508
8509                /* ============================================================
8510                 * UPDATE CART TOTAL
8511                 * ============================================================ */
8512
8513                if (result.cart) {
8514                    updateCartTotal(result);
8515                }
8516                /* ----------------------------------------------------
8517                 * CART CONTENT
8518                 * ---------------------------------------------------- */
8519
8520                const cartElement =
8521                    document.querySelector(
8522                        '#LOXSHOPCART'
8523                    );
8524
8525                if (cartElement) {
8526
8527                    /*
8528                     * تمام ردیف‌های محصول را حذف می‌کنیم.
8529                     *
8530                     * ساختار HTML را تغییر نمی‌دهیم.
8531                     */
8532
8533                    const rows =
8534                        cartElement.querySelectorAll(
8535                            'tr[data-cart-item-id]'
8536                        );
8537
8538                    rows.forEach(function (row) {
8539
8540                        row.remove();
8541
8542                    });
8543                }
8544
8545
8546                /* ----------------------------------------------------
8547                 * SUCCESS
8548                 * ---------------------------------------------------- */
8549
8550                console.log(
8551                    'Cart cleared successfully.'
8552                );
8553
8554
8555                /*
8556                 * اگر بعداً خواستیم بعد از خالی شدن سبد،
8557                 * کل HTML سبد را از PHP بگیریم،
8558                 * این قسمت را می‌توانیم توسعه بدهیم.
8559                 */
8560
8561            })
8562
8563
8564            /* --------------------------------------------------------
8565             * ERROR
8566             * -------------------------------------------------------- */
8567
8568            .catch(function (error) {
8569
8570                console.error(
8571                    'Clear cart error:',
8572                    error
8573                );
8574
8575
8576                alert(
8577                    error.message ||
8578                    'خطا در خالی کردن سبد خرید.'
8579                );
8580
8581            })
8582
8583
8584            /* --------------------------------------------------------
8585             * FINALLY
8586             * -------------------------------------------------------- */
8587
8588            .finally(function () {
8589
8590                if (button) {
8591
8592                    button.dataset.loading =
8593                        '0';
8594
8595                    button.disabled =
8596                        false;
8597
8598                    if (
8599                        button.dataset.originalText
8600                    ) {
8601
8602                        button.innerHTML =
8603                            button.dataset.originalText;
8604                    }
8605                }
8606
8607            });
8608
8609
8610        return false;
8611    }
8612
8613
8614    /* ============================================================
8615     * INTERCEPT CLEAR BUTTON
8616     *
8617     * HTML فعلی دست نخورده باقی می‌ماند.
8618     *
8619     * دکمه فعلی:
8620     *
8621     * name="command"
8622     * value="clear"
8623     *
8624     * ابتدا در CAPTURE گرفته می‌شود تا onclick قدیمی
8625     * که load_ajax را اجرا می‌کند، اصلاً اجرا نشود.
8626     * ============================================================ */
8627
8628    document.addEventListener(
8629        'click',
8630        function (event) {
8631
8632            const button =
8633                event.target.closest
8634                    ? event.target.closest(
8635                        '#form_cart button[name="command"][value="clear"]'
8636                    )
8637                    : null;
8638
8639            if (!button) {
8640                return;
8641            }
8642
8643
8644            /* ----------------------------------------------------
8645             * STOP OLD load_ajax()
8646             * ---------------------------------------------------- */
8647
8648            event.preventDefault();
8649
8650            event.stopPropagation();
8651
8652            event.stopImmediatePropagation();
8653
8654
8655            /* ----------------------------------------------------
8656             * CLEAR CART
8657             * ---------------------------------------------------- */
8658
8659            clearCart();
8660
8661        },
8662        true
8663    );
8664
8665
8666})();
8667
8668
8669
8670
8671
8672document.addEventListener('click', function (e) {
8673    const toggle = e.target.closest('.lx_dropdown-toggle');
8674
8675    if (toggle) {
8676        e.preventDefault();
8677        e.stopPropagation();
8678        toggle.classList.toggle('lx_show-dropdown');
8679    } else if (!e.target.closest('.lx_dropdown-menu')) {
8680        document.querySelectorAll('.lx_show-dropdown').forEach(function (el) {
8681            el.classList.remove('lx_show-dropdown');
8682        });
8683    }
8684});
8685
8686
8687
8688
8689
8690
8691
8692
8693document.addEventListener('click', function (e) {
8694
8695    var button = e.target.closest('.new_addres');
8696
8697    if (!button) {
8698        return;
8699    }
8700
8701    var form = document.querySelector('.form_add_addres');
8702
8703    if (!form) {
8704        return;
8705    }
8706
8707    form.style.display = 'block';
8708
8709});
8710
8711
8712
8713
8714
8715
8716
8717
8718
8719
8720
8721
8722
8723
8724
8725
8726
8727(function () {
8728
8729    'use strict';
8730
8731
8732    /* ================================================================
8733     * GATEWAY CHANGE
8734     * ================================================================ */
8735
8736    $(document).on(
8737        'change',
8738        '.pending-order-gateway',
8739        function () {
8740
8741            var $select =
8742                $(this);
8743
8744            var $box =
8745                $select.closest(
8746                    '[data-pending-order-payment="1"]'
8747                );
8748
8749            var gatewayId =
8750                $select.val();
8751
8752            var $button =
8753                $box.find(
8754                    '.pending-order-pay-btn'
8755                );
8756
8757            var $cardTransfer =
8758                $box.find(
8759                    '[data-order-card-transfer="1"]'
8760                );
8761
8762
8763            /*
8764             * Reset
8765             */
8766
8767            $button
8768                .prop('disabled', !gatewayId);
8769
8770            $cardTransfer
8771                .hide()
8772                .empty();
8773
8774            $box
8775                .find('[data-payment-response="1"]')
8776                .hide()
8777                .empty();
8778        }
8779    );
8780
8781
8782    /* ================================================================
8783     * PAY / RETRY
8784     * ================================================================ */
8785
8786    $(document).on(
8787        'click',
8788        '.pending-order-pay-btn',
8789        function (e) {
8790
8791            e.preventDefault();
8792
8793
8794            var $button =
8795                $(this);
8796
8797            var $box =
8798                $button.closest(
8799                    '[data-pending-order-payment="1"]'
8800                );
8801
8802            var orderId =
8803                parseInt(
8804                    $button.attr(
8805                        'data-order-id'
8806                    ),
8807                    10
8808                ) || 0;
8809
8810
8811            var gatewayId =
8812                parseInt(
8813                    $box
8814                        .find('.pending-order-gateway')
8815                        .val(),
8816                    10
8817                ) || 0;
8818
8819
8820            if (!orderId) {
8821                return;
8822            }
8823
8824
8825            if (!gatewayId) {
8826                alert(
8827                    'لطفاً درگاه پرداخت را انتخاب کنید.'
8828                );
8829
8830                return;
8831            }
8832
8833
8834            $button
8835                .prop('disabled', true)
8836                .addClass('disabled');
8837
8838
8839            $box
8840                .find('[data-payment-response="1"]')
8841                .hide()
8842                .empty();
8843
8844
8845            $.ajax({
8846
8847                url:
8848                    '/coreshop/ajax.php?action=retry_order_payment',
8849
8850                type: 'POST',
8851
8852                dataType: 'json',
8853
8854                data: {
8855                    order_id: orderId,
8856                    gateway_id: gatewayId
8857                },
8858
8859                headers: {
8860                    'X-Requested-With':
8861                        'XMLHttpRequest'
8862                },
8863
8864                success: function (response) {
8865
8866                    if (
8867                        !response ||
8868                        !response.success
8869                    ) {
8870
8871                        showPendingPaymentError(
8872                            $box,
8873                            response &&
8874                                response.message
8875                                ? response.message
8876                                : 'پرداخت انجام نشد.'
8877                        );
8878
8879                        $button
8880                            .prop('disabled', false)
8881                            .removeClass('disabled');
8882
8883                        return;
8884                    }
8885
8886
8887                    /* ----------------------------------------------------
8888                     * ONLINE
8889                     * ---------------------------------------------------- */
8890
8891                    if (
8892                        response.type === 'online'
8893                    ) {
8894
8895                        if (
8896                            response.redirect_url
8897                        ) {
8898
8899                            window.location.href =
8900                                response.redirect_url;
8901
8902                            return;
8903                        }
8904
8905
8906                        showPendingPaymentError(
8907                            $box,
8908                            'آدرس درگاه پرداخت دریافت نشد.'
8909                        );
8910
8911                        $button
8912                            .prop('disabled', false)
8913                            .removeClass('disabled');
8914
8915                        return;
8916                    }
8917
8918
8919                    /* ----------------------------------------------------
8920                     * CARD TO CARD
8921                     * ---------------------------------------------------- */
8922
8923                    if (
8924                        response.type === 'card_to_card'
8925                    ) {
8926
8927                        renderPendingCardTransferForm(
8928                            $box,
8929                            response
8930                        );
8931
8932
8933                        $button
8934                            .prop('disabled', true)
8935                            .addClass('disabled');
8936
8937                        return;
8938                    }
8939
8940
8941                    showPendingPaymentError(
8942                        $box,
8943                        'نوع پرداخت نامعتبر است.'
8944                    );
8945
8946
8947                    $button
8948                        .prop('disabled', false)
8949                        .removeClass('disabled');
8950                },
8951
8952                error: function (xhr) {
8953
8954                    var message =
8955                        'خطا در برقراری ارتباط با سرور.';
8956
8957
8958                    if (
8959                        xhr.responseJSON &&
8960                        xhr.responseJSON.message
8961                    ) {
8962                        message =
8963                            xhr.responseJSON.message;
8964                    }
8965
8966
8967                    showPendingPaymentError(
8968                        $box,
8969                        message
8970                    );
8971
8972
8973                    $button
8974                        .prop('disabled', false)
8975                        .removeClass('disabled');
8976                }
8977            });
8978        }
8979    );
8980
8981
8982    /* ================================================================
8983     * CARD TO CARD FORM
8984     * ================================================================ */
8985
8986    function renderPendingCardTransferForm(
8987        $box,
8988        response
8989    ) {
8990
8991        var $container =
8992            $box.find(
8993                '[data-order-card-transfer="1"]'
8994            );
8995
8996
8997        var gateway =
8998            response.gateway || {};
8999
9000        var settings =
9001            gateway.settings || {};
9002
9003
9004        var amount =
9005            parseFloat(
9006                response.amount || 0
9007            );
9008
9009
9010        var amountFormatted =
9011            numberFormat(
9012                amount
9013            );
9014
9015
9016        var bankName =
9017            settings.bank_name || '';
9018
9019        var cardNumber =
9020            settings.card_number || '';
9021
9022        var cardHolder =
9023            settings.card_holder || '';
9024
9025        var iban =
9026            settings.iban || '';
9027
9028        var gatewayDescription =
9029            settings.description || '';
9030
9031
9032        var html = '';
9033
9034        html +=
9035            '<div class="shop-order-card-transfer-form">';
9036
9037
9038        html +=
9039            '<div class="card-transfer-header">';
9040
9041        html +=
9042            '<strong>ثبت اطلاعات کارت به کارت</strong>';
9043
9044        html +=
9045            '</div>';
9046
9047        if (gatewayDescription) {
9048
9049            html +=
9050                '<div class="alert alert-info mb-3">';
9051
9052            html +=
9053                escapeHtml(
9054                    gatewayDescription
9055                );
9056
9057            html +=
9058                '</div>';
9059        }
9060        if (bankName) {
9061
9062            html +=
9063                '<div class="mb-2">';
9064
9065            html +=
9066                '<strong>بانک:</strong> ';
9067
9068            html +=
9069                escapeHtml(bankName);
9070
9071            html +=
9072                '</div>';
9073        }
9074
9075
9076        if (cardNumber) {
9077
9078            html +=
9079                '<div class="mb-2">';
9080
9081            html +=
9082                '<strong>شماره کارت:</strong> ';
9083
9084            html +=
9085                '<span dir="ltr">'
9086                + escapeHtml(cardNumber)
9087                + '</span>';
9088
9089            html +=
9090                '</div>';
9091        }
9092
9093
9094        if (cardHolder) {
9095
9096            html +=
9097                '<div class="mb-2">';
9098
9099            html +=
9100                '<strong>به نام:</strong> ';
9101
9102            html +=
9103                escapeHtml(cardHolder);
9104
9105            html +=
9106                '</div>';
9107        }
9108
9109
9110        if (iban) {
9111
9112            html +=
9113                '<div class="mb-2">';
9114
9115            html +=
9116                '<strong>شبا:</strong> ';
9117
9118            html +=
9119                '<span dir="ltr">'
9120                + escapeHtml(iban)
9121                + '</span>';
9122
9123            html +=
9124                '</div>';
9125        }
9126
9127
9128        html +=
9129            '<div class="mb-3">';
9130
9131        html +=
9132            '<strong>مبلغ قابل پرداخت:</strong> ';
9133
9134        html +=
9135            '<span>'
9136            + amountFormatted
9137            + ' تومان'
9138            + '</span>';
9139
9140        html +=
9141            '</div>';
9142
9143
9144
9145
9146
9147        html +=
9148            '<form'
9149            + ' class="pending-order-card-transfer-form"'
9150            + ' enctype="multipart/form-data"'
9151            + '>';
9152
9153        html +=
9154            '<input'
9155            + ' type="hidden"'
9156            + ' name="order_id"'
9157            + ' value="'
9158            + parseInt(response.order_id, 10)
9159            + '">';
9160
9161        html +=
9162            '<input'
9163            + ' type="hidden"'
9164            + ' name="payment_id"'
9165            + ' value="'
9166            + parseInt(response.payment_id, 10)
9167            + '">';
9168
9169
9170        /* ------------------------------------------------------------
9171         * DATE
9172         * ------------------------------------------------------------ */
9173
9174        html +=
9175            '<div class="mb-3">';
9176
9177        html +=
9178            '<label>تاریخ پرداخت</label>';
9179
9180        html +=
9181            '<input'
9182            + ' type="text"'
9183            + ' name="paid_date"'
9184            + ' class="form-control"'
9185            + ' placeholder="1405/06/19"'
9186            + ' required>';
9187
9188        html +=
9189            '</div>';
9190
9191
9192        /* ------------------------------------------------------------
9193         * TIME
9194         * ------------------------------------------------------------ */
9195
9196        html +=
9197            '<div class="mb-3">';
9198
9199        html +=
9200            '<label>ساعت پرداخت</label>';
9201
9202        html +=
9203            '<input'
9204            + ' type="text"'
9205            + ' name="paid_time"'
9206            + ' class="form-control"'
9207            + ' value="12:00"'
9208            + ' required>';
9209
9210        html +=
9211            '</div>';
9212
9213
9214        /* ------------------------------------------------------------
9215         * REFERENCE
9216         * ------------------------------------------------------------ */
9217
9218        html +=
9219            '<div class="mb-3">';
9220
9221        html +=
9222            '<label>شماره پیگیری / کد مرجع</label>';
9223
9224        html +=
9225            '<input'
9226            + ' type="text"'
9227            + ' name="reference_code"'
9228            + ' class="form-control"'
9229            + ' required>';
9230
9231        html +=
9232            '</div>';
9233
9234
9235        /* ------------------------------------------------------------
9236         * SENDER NAME
9237         * ------------------------------------------------------------ */
9238
9239        html +=
9240            '<div class="mb-3">';
9241
9242        html +=
9243            '<label>نام پرداخت‌کننده</label>';
9244
9245        html +=
9246            '<input'
9247            + ' type="text"'
9248            + ' name="sender_name"'
9249            + ' class="form-control"'
9250            + ' required>';
9251
9252        html +=
9253            '</div>';
9254
9255
9256        /* ------------------------------------------------------------
9257         * CARD LAST 4
9258         * ------------------------------------------------------------ */
9259
9260        html +=
9261            '<div class="mb-3">';
9262
9263        html +=
9264            '<label>چهار رقم آخر کارت</label>';
9265
9266        html +=
9267            '<input'
9268            + ' type="text"'
9269            + ' name="sender_card_last4"'
9270            + ' maxlength="4"'
9271            + ' class="form-control"'
9272            + ' inputmode="numeric">';
9273
9274        html +=
9275            '</div>';
9276
9277
9278        /* ------------------------------------------------------------
9279         * DESCRIPTION
9280         * ------------------------------------------------------------ */
9281
9282        html +=
9283            '<div class="mb-3">';
9284
9285        html +=
9286            '<label>توضیحات</label>';
9287
9288        html +=
9289            '<textarea'
9290            + ' name="description"'
9291            + ' class="form-control"'
9292            + ' rows="3">'
9293            + '</textarea>';
9294
9295        html +=
9296            '</div>';
9297
9298
9299        /* ------------------------------------------------------------
9300         * RECEIPT
9301         * ------------------------------------------------------------ */
9302
9303        html +=
9304            '<div class="mb-3">';
9305
9306        html +=
9307            '<label>تصویر رسید پرداخت</label>';
9308
9309        html +=
9310            '<input'
9311            + ' type="file"'
9312            + ' name="receipt"'
9313            + ' class="form-control"'
9314            + ' accept="image/jpeg,image/png,image/webp">';
9315
9316        html +=
9317            '<small class="text-muted">'
9318            + 'حداکثر حجم: ۵ مگابایت'
9319            + '</small>';
9320
9321        html +=
9322            '</div>';
9323
9324
9325        /* ------------------------------------------------------------
9326         * SUBMIT
9327         * ------------------------------------------------------------ */
9328
9329        html +=
9330            '<button'
9331            + ' type="submit"'
9332            + ' class="btn btn-success pending-order-submit-card-transfer">'
9333            + '<i class="fa fa-check"></i> '
9334            + 'ثبت اطلاعات پرداخت'
9335            + '</button>';
9336
9337
9338        html +=
9339            '<div'
9340            + ' class="pending-order-card-transfer-response mt-3"'
9341            + ' style="display:none;">'
9342            + '</div>';
9343
9344
9345        html +=
9346            '</form>';
9347
9348
9349        html +=
9350            '</div>';
9351
9352
9353        $container
9354            .html(html)
9355            .slideDown();
9356    }
9357
9358
9359    /* ================================================================
9360     * SUBMIT CARD TO CARD
9361     * ================================================================ */
9362
9363    $(document).on(
9364        'submit',
9365        '.pending-order-card-transfer-form',
9366        function (e) {
9367
9368            e.preventDefault();
9369
9370
9371            var $form =
9372                $(this);
9373
9374            var $box =
9375                $form.closest(
9376                    '[data-pending-order-payment="1"]'
9377                );
9378
9379            var $submit =
9380                $form.find(
9381                    '.pending-order-submit-card-transfer'
9382                );
9383
9384            var $response =
9385                $form.find(
9386                    '.pending-order-card-transfer-response'
9387                );
9388
9389
9390            var formData =
9391                new FormData(
9392                    this
9393                );
9394
9395
9396            $submit
9397                .prop('disabled', true)
9398                .addClass('disabled');
9399
9400
9401            $response
9402                .hide()
9403                .empty();
9404
9405
9406            $.ajax({
9407
9408                url:
9409                    '/coreshop/ajax.php?action=submit_order_payment_submission',
9410
9411                type: 'POST',
9412
9413                data:
9414                    formData,
9415
9416                processData:
9417                    false,
9418
9419                contentType:
9420                    false,
9421
9422                dataType:
9423                    'json',
9424
9425                headers: {
9426                    'X-Requested-With':
9427                        'XMLHttpRequest'
9428                },
9429
9430                success: function (response) {
9431
9432                    if (
9433                        response &&
9434                        response.success
9435                    ) {
9436
9437                        $response
9438                            .html(
9439                                '<div class="alert alert-success">'
9440                                + escapeHtml(
9441                                    response.message ||
9442                                    'اطلاعات پرداخت با موفقیت ثبت شد.'
9443                                )
9444                                + '</div>'
9445                            )
9446                            .show();
9447
9448
9449                        $form
9450                            .find('input, textarea, button, select')
9451                            .prop('disabled', true);
9452
9453
9454                        return;
9455                    }
9456
9457
9458                    $response
9459                        .html(
9460                            '<div class="alert alert-danger">'
9461                            + escapeHtml(
9462                                response &&
9463                                    response.message
9464                                    ? response.message
9465                                    : 'ثبت اطلاعات پرداخت انجام نشد.'
9466                            )
9467                            + '</div>'
9468                        )
9469                        .show();
9470
9471
9472                    $submit
9473                        .prop('disabled', false)
9474                        .removeClass('disabled');
9475                },
9476
9477                error: function (xhr) {
9478
9479                    var message =
9480                        'خطا در ثبت اطلاعات پرداخت.';
9481
9482
9483                    if (
9484                        xhr.responseJSON &&
9485                        xhr.responseJSON.message
9486                    ) {
9487                        message =
9488                            xhr.responseJSON.message;
9489                    }
9490
9491
9492                    $response
9493                        .html(
9494                            '<div class="alert alert-danger">'
9495                            + escapeHtml(message)
9496                            + '</div>'
9497                        )
9498                        .show();
9499
9500
9501                    $submit
9502                        .prop('disabled', false)
9503                        .removeClass('disabled');
9504                }
9505            });
9506        }
9507    );
9508
9509
9510    /* ================================================================
9511     * HELPERS
9512     * ================================================================ */
9513
9514    function showPendingPaymentError(
9515        $box,
9516        message
9517    ) {
9518
9519        $box
9520            .find('[data-payment-response="1"]')
9521            .html(
9522                '<div class="alert alert-danger">'
9523                + escapeHtml(message)
9524                + '</div>'
9525            )
9526            .show();
9527    }
9528
9529
9530    function numberFormat(number) {
9531
9532        number =
9533            parseFloat(number) || 0;
9534
9535        return number
9536            .toLocaleString('en-US', {
9537                maximumFractionDigits: 0
9538            });
9539    }
9540
9541
9542    function escapeHtml(value) {
9543
9544        return $('<div>')
9545            .text(value == null ? '' : value)
9546            .html();
9547    }
9548
9549})();
9550
9551
9552
9553$(document).on(
9554    'click',
9555    '.lx-color-swatch',
9556    function (e) {
9557
9558        e.preventDefault();
9559
9560        var $swatch = $(this);
9561
9562        var value = String(
9563            $swatch.attr('data-value') || ''
9564        );
9565
9566        var $variationValues =
9567            $swatch.closest('.variation-values');
9568
9569        var $select =
9570            $variationValues.find(
9571                'select.product_var'
9572            );
9573
9574        if (!$select.length) {
9575            return;
9576        }
9577
9578        /*
9579        |--------------------------------------------------------------------------
9580        | Set SELECT value
9581        |--------------------------------------------------------------------------
9582        */
9583
9584        $select.val(value);
9585
9586        /*
9587        |--------------------------------------------------------------------------
9588        | Update visual active state
9589        |--------------------------------------------------------------------------
9590        */
9591
9592        $variationValues
9593            .find('.lx-color-swatch')
9594            .removeClass('active');
9595
9596        $swatch.addClass('active');
9597
9598        /*
9599        |--------------------------------------------------------------------------
9600        | Native change event
9601        |--------------------------------------------------------------------------
9602        */
9603
9604        var selectElement =
9605            $select.get(0);
9606
9607        if (selectElement) {
9608
9609            selectElement.dispatchEvent(
9610                new Event(
9611                    'change',
9612                    {
9613                        bubbles: true
9614                    }
9615                )
9616            );
9617
9618        }
9619
9620    }
9621);
9622
9623
9624
9625
9626if( document.getElementsByClassName('product_cat')[0] )
9627{
9628	var $selected;
9629	var $selected2;
9630	var $selected3;
9631    const pp = new URL(window.location);
9632    const c = new URLSearchParams(pp.search);
9633	var jsonElement = document.getElementById('list_category');
9634	var list_cat = JSON.parse(jsonElement.textContent);
9635	if(list_cat)
9636	{
9637		var list_option = '<option value="0" selected="selected">دسته بندی</option>';
9638		Object.entries(list_cat).forEach(([key, value]) => 
9639		{
9640			if( c.get("cat") == key ) $selected = 'selected';else $selected = ''
9641			list_option += '<option class="level-0" value="'+key+'" '+$selected+'>'+value['title']+'</option>';
9642			if( value['child'] )
9643			{
9644				Object.entries(value['child']).forEach(([key2, value2]) => 
9645				{
9646					if( value2['child'] )
9647					{
9648						if( c.get("cat") == key2 ) $selected2 = 'selected';else $selected2 = ''
9649						list_option += '<option class="level-1" value="'+key2+'" style="padding-right:15px" '+$selected2+'>   '+value2['title']+'</option>';
9650						Object.entries(value2['child']).forEach(([key3, value3]) => 
9651						{
9652							if( c.get("cat") == key3 ) $selected3 = 'selected';else $selected3 = ''
9653							list_option += '<option class="level-2" value="'+key3+'" style="padding-right:25px" '+$selected3+'>      '+value3['title']+'</option>';
9654						})
9655					}
9656				})
9657			}
9658		})
9659		var elems = document.getElementsByClassName('product_cat');
9660		for (let i = 0; i < elems.length; i++) 
9661		{
9662			elems[i].innerHTML = list_option;
9663		}
9664	}
9665}
9666
9667
9668
9669
9670

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.