1// Sepet â Teklif Gönder akıÅı 2// Satıcının "Teklif Gönder" butonuna tıklandıÄında modal açar, 3// kullanıcı tutar girer, %X minimum doÄrulamasını uygular, sonra 4// /Marketplace/CreateOffer endpoint'ine POST eder. 5 6(function () { 7 'use strict'; 8 9 // BUG #3 FIX: CSRF token okuyucu (message-widget.js deseniyle birebir) 10 function getAntiForgeryToken() { 11 return document.querySelector('input[name="__RequestVerificationToken"]')?.value || ''; 12 } 13 14 function formatTry(amount) { 15 if (typeof amount !== 'number' || isNaN(amount)) return '0,00'; 16 try { 17 return window.BinderimFormat.formatNumber(amount, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); 18 } catch (e) { 19 return amount.toFixed(2); 20 } 21 } 22 23 function showToast(message, type) { 24 if (window.toastr) { 25 (window.toastr[type] || window.toastr.info)(message); 26 } else { 27 // eslint-disable-next-line no-alert 28 alert(message); 29 } 30 } 31 32 function getSellerCartTotalFromDom(sellerId) { 33 // Satıcı grubundaki tüm basket-item'ları toplar 34 var group = document.querySelector('.basket-seller-group[data-seller-id="' + cssEscape(sellerId) + '"]'); 35 if (!group) return 0; 36 37 var items = group.querySelectorAll('.basket-item'); 38 var total = 0; 39 items.forEach(function (item) { 40 var price = parseFloat(item.getAttribute('data-price') || '0'); 41 var qty = parseInt(item.getAttribute('data-quantity') || '0', 10); 42 if (!isNaN(price) && !isNaN(qty)) { 43 total += price * qty; 44 } 45 }); 46 return total; 47 } 48 49 function cssEscape(str) { 50 if (typeof CSS !== 'undefined' && CSS.escape) { 51 return CSS.escape(str); 52 } 53 return String(str).replace(/[^a-zA-Z0-9_-]/g, function (ch) { 54 return '\\' + ch; 55 }); 56 } 57 58 var modalEl = document.getElementById('basketOfferModal'); 59 if (!modalEl || typeof bootstrap === 'undefined' || !bootstrap.Modal) { 60 return; 61 } 62 63 // ---- i18n (Issue #1625 B) ---------------------------------------------------- 64 // Kullaniciya gorunen metinler artik bu dosyada SABIT DEGIL: Basket.cshtml basketOfferModal 65 // uzerine data-i18n-* attribute'lari basar, sunucu onlari Basket.resx/Basket.en.resx'ten 66 // doldurur. Ayni desen basket.js (#1625 B)/destek.js (#1613). Attribute yoksa (or. onbellekten 67 // gelen eski HTML) bugunku TR metne duseriz -- davranis degismez. 68 function t(name, fallback) { 69 var value = modalEl.getAttribute('data-i18n-' + name); 70 return (value === null || value === '') ? fallback : value; 71 } 72 /** '{0}'/'{1}'/'{2}' yer tutucularini argumanlarla degistirir (wallet-payout.js'teki coklu-arg 73 * fmt() ile ayni sozlesme). */ 74 // QA (#1625 B): innerHTML'e gomulen ceviri degeri escape edilir (basket.js / 75 // marketplace-redesign.js ile BIREBIR ayni; cift kacis yok, getAttribute decode eder). 76 function escapeHtml(value) { 77 return String(value == null ? '' : value) 78 .replace(/&/g, '&') 79 .replace(/</g, '<') 80 .replace(/>/g, '>') 81 .replace(/"/g, '"') 82 .replace(/'/g, '''); 83 } 84 85 function fmt(template) { 86 var args = Array.prototype.slice.call(arguments, 1); 87 return String(template == null ? '' : template).replace(/\{(\d+)\}/g, function (match, index) { 88 var v = args[Number(index)]; 89 return v === undefined ? match : v; 90 }); 91 } 92 93 var modal = bootstrap.Modal.getOrCreateInstance(modalEl); 94 95 var elSellerName = document.getElementById('basketOfferSellerName'); 96 var elCartTotal = document.getElementById('basketOfferCartTotal'); 97 var elMinimum = document.getElementById('basketOfferMinimum'); 98 var elInfoText = document.getElementById('basketOfferInfoText'); 99 var elAmount = document.getElementById('basketOfferAmount'); 100 var elCurrency = document.getElementById('basketOfferCurrency'); 101 var elNote = document.getElementById('basketOfferNote'); 102 var elError = document.getElementById('basketOfferError'); 103 var elSubmit = document.getElementById('basketOfferSubmitBtn'); 104 105 var state = { 106 sellerId: '', 107 sellerName: '', 108 currency: 'TRY', 109 cartTotal: 0, 110 minPercentage: 70, 111 minAmount: 0 112 }; 113 114 function setError(message) { 115 if (!elError) return; 116 if (message) { 117 elError.textContent = message; 118 elError.classList.remove('d-none'); 119 } else { 120 elError.textContent = ''; 121 elError.classList.add('d-none'); 122 } 123 } 124 125 function openOfferModal(button) { 126 if (button.getAttribute('data-has-pending') === 'true') { 127 showToast(t('offer-already-pending', 'Bu satıcıya zaten aktif bir teklifiniz var.'), 'warning'); 128 return; 129 } 130 if (button.getAttribute('data-has-accepted') === 'true') { 131 showToast(t('offer-already-accepted', 'Bu satıcı için kabul edilmiÅ bir teklifiniz var. Sepetiniz indirimli görünüyor.'), 'info'); 132 return; 133 } 134 135 state.sellerId = button.getAttribute('data-seller-id') || ''; 136 state.sellerName = button.getAttribute('data-seller-name') || t('default-seller-name', 'Satıcı'); 137 state.currency = button.getAttribute('data-currency') || 'TRY'; 138 state.minPercentage = parseFloat(button.getAttribute('data-min-percentage') || '70'); 139 if (isNaN(state.minPercentage) || state.minPercentage <= 0) { 140 state.minPercentage = 70; 141 } 142 143 state.cartTotal = getSellerCartTotalFromDom(state.sellerId); 144 state.minAmount = Math.round(state.cartTotal * (state.minPercentage / 100) * 100) / 100; 145 146 if (elSellerName) elSellerName.textContent = state.sellerName; 147 if (elCartTotal) elCartTotal.textContent = formatTry(state.cartTotal) + ' ' + state.currency; 148 if (elMinimum) elMinimum.textContent = formatTry(state.minAmount) + ' ' + state.currency;
149 if (elCurrency) elCurrency.textContent = state.currency; 150 if (elInfoText) { 151 var infoPercentage = state.minPercentage % 1 === 0 ? state.minPercentage.toFixed(0) : state.minPercentage; 152 elInfoText.textContent = fmt(t('offer-info-template', 'Sepet toplamının %{0}\'inin altında teklif gönderilemez. Satıcı kabul ederse indirim sepetteki kartlara orantılı uygulanır.'), infoPercentage); 153 } 154 if (elAmount) { 155 elAmount.value = ''; 156 elAmount.min = state.minAmount; 157 elAmount.max = state.cartTotal; 158 } 159 if (elNote) elNote.value = ''; 160 setError(''); 161 162 modal.show(); 163 } 164 165 function validateAmount(amount) { 166 if (typeof amount !== 'number' || isNaN(amount) || amount <= 0) { 167 return t('offer-amount-required', 'Lütfen geçerli bir teklif tutarı girin.'); 168 } 169 if (amount < state.minAmount) { 170 var minPercentage = state.minPercentage % 1 === 0 ? state.minPercentage.toFixed(0) : state.minPercentage; 171 return fmt(t('offer-minimum-template', 'Teklif en az {0} {1} olmalıdır (sepetin %{2}\'i).'), formatTry(state.minAmount), state.currency, minPercentage); 172 } 173 if (amount >= state.cartTotal) { 174 return t('offer-below-total', 'Teklif sepetin toplamından düÅük olmalıdır.'); 175 } 176 return null; 177 } 178 179 async function submitOffer() { 180 if (!state.sellerId) { 181 return; 182 } 183 184 var amount = window.BinderimFormat.parseInput(elAmount); 185 186 var err = validateAmount(amount); 187 if (err) { 188 setError(err); 189 return; 190 } 191 192 setError(''); 193 elSubmit.disabled = true; 194 var originalText = elSubmit.innerHTML; 195 elSubmit.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> ' + escapeHtml(t('offer-submitting-button', 'Gönderiliyor...')); 196 197 try { 198 var response = await fetch('/Marketplace/CreateOffer', { 199 method: 'POST', 200 headers: { 201 'Content-Type': 'application/json', 202 'RequestVerificationToken': getAntiForgeryToken() 203 }, 204 body: JSON.stringify({ 205 sellerId: state.sellerId, 206 amount: amount, 207 note: elNote ? elNote.value : null 208 }) 209 }); 210 var data = await response.json(); 211 212 if (data && data.success) { 213 modal.hide(); 214 showToast(data.message || t('offer-submitted-fallback', 'Teklifiniz iletildi.'), 'success'); 215 // Butonu güncelle 216 var btn = document.querySelector('.btn-offer-seller[data-seller-id="' + cssEscape(state.sellerId) + '"]'); 217 if (btn) { 218 btn.setAttribute('data-has-pending', 'true'); 219 btn.classList.add('is-pending'); 220 var span = btn.querySelector('span'); 221 if (span) span.textContent = t('offer-pending-label', 'Teklif Beklemede'); 222 } 223 } else { 224 setError((data && data.message) || t('offer-submit-failed-fallback', 'Teklif gönderilemedi.')); 225 } 226 } catch (ex) { 227 setError(t('offer-submit-error-fallback', 'Teklif gönderilirken bir hata oluÅtu.')); 228 } finally { 229 elSubmit.disabled = false; 230 elSubmit.innerHTML = originalText; 231 } 232 } 233 234 async function withdrawOffer(offerId) { 235 if (!offerId) return; 236 // eslint-disable-next-line no-alert 237 if (!window.confirm(t('offer-withdraw-confirm', 'Teklifinizi geri çekmek istediÄinize emin misiniz?'))) return; 238 239 try { 240 var response = await fetch('/Marketplace/WithdrawOffer', { 241 method: 'POST', 242 headers: { 243 'Content-Type': 'application/json', 244 'RequestVerificationToken': getAntiForgeryToken() 245 }, 246 body: JSON.stringify({ offerId: Number(offerId) }) 247 }); 248 var data = await response.json(); 249 250 if (data && data.success) { 251 showToast(data.message || t('offer-withdrawn-fallback', 'Teklifiniz geri çekildi.'), 'success'); 252 // Teklif butonunu "Teklif Gönder" durumuna geri al 253 var offerBtn = document.querySelector('.btn-offer-seller[data-pending-offer-id="' + offerId + '"]'); 254 if (offerBtn) { 255 offerBtn.setAttribute('data-has-pending', 'false'); 256 offerBtn.setAttribute('data-pending-offer-id', ''); 257 offerBtn.classList.remove('is-pending'); 258 var span = offerBtn.querySelector('span'); 259 if (span) span.textContent = t('offer-send-label', 'Teklif Gönder'); 260 } 261 // Geri çek butonunu gizle 262 var withdrawBtn = document.querySelector('.btn-withdraw-offer[data-pending-offer-id="' + offerId + '"]'); 263 if (withdrawBtn) withdrawBtn.remove(); 264 } else { 265 showToast((data && data.message) || t('offer-withdraw-failed-fallback', 'Teklif geri çekilemedi.'), 'error'); 266 } 267 } catch (ex) { 268 showToast(t('offer-withdraw-error-fallback', 'Teklif geri çekilirken bir hata oluÅtu.'), 'error'); 269 } 270 } 271 272 // Sayfa içindeki tüm Teklif Gönder butonları için ortak handler 273 document.addEventListener('click', function (event) { 274 // Geri çek butonu 275 var withdrawBtn = event.target.closest('.btn-withdraw-offer'); 276 if (withdrawBtn) { 277 event.preventDefault(); 278 var offerId = withdrawBtn.getAttribute('data-pending-offer-id'); 279 if (offerId) withdrawOffer(offerId); 280 return; 281 } 282 283 var btn = event.target.closest('.btn-offer-seller'); 284 if (!btn) return; 285 event.preventDefault(); 286 openOfferModal(btn); 287 }); 288 289 if (elSubmit) { 290 elSubmit.addEventListener('click', submitOffer); 291 } 292 293 if (elAmount) {
294 elAmount.addEventListener('input', function () { 295 setError(''); 296 }); 297 elAmount.addEventListener('keydown', function (e) { 298 if (e.key === 'Enter') { 299 e.preventDefault(); 300 submitOffer(); 301 } 302 }); 303 } 304})();
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.