diff --git a/frontend.py b/frontend.py
index c1267d5..dda3ede 100644
--- a/frontend.py
+++ b/frontend.py
@@ -348,7 +348,7 @@ async def profile_page(
inventory_items = db.query(InventoryItem).filter(
InventoryItem.user_id == user.id
- ).all()
+ ).order_by(desc(InventoryItem.obtained_at)).all()
valuable_items = sorted(
inventory_items,
@@ -356,15 +356,52 @@ async def profile_page(
reverse=True
)[:12]
+ rarities = set()
+ types = set()
+ enriched_items = []
+ for item in inventory_items:
+ item_data = None
+ for idx, it in enumerate(ALL_ITEMS):
+ if it.get("_id", idx) == item.item_id:
+ item_data = it
+ break
+ if not item_data:
+ item_data = get_item(item.item_id)
+ image_url = ""
+ price_rub = 100
+ if item_data:
+ image_url = item_data.get("image_url", "")
+ price_rub = item_data.get("price_rub", 100)
+ enriched_items.append({
+ "id": item.id,
+ "item_id": item.item_id,
+ "name": item.market_hash_name,
+ "rarity": item.rarity,
+ "wear": item.wear,
+ "float": item.float_value,
+ "type": item.type,
+ "image_url": image_url,
+ "price_rub": price_rub
+ })
+ if item.rarity:
+ rarities.add(item.rarity)
+ if item.type:
+ types.add(item.type)
+
return templates.TemplateResponse("profile.html", {
"request": request,
"user": user,
"total_items": total_items,
"cases_opened": cases_opened,
"contracts_completed": contracts_completed,
+ "balance": user.balance,
"recent_openings": recent_openings,
"recent_contracts": recent_contracts,
"valuable_items": valuable_items,
+ "inventory_items": enriched_items,
+ "rarities": sorted(rarities),
+ "types": sorted(types),
+ "rarity_order": RARITY_ORDER,
"is_public": False,
"profile_user": user
})
@@ -564,66 +601,9 @@ async def contracts_page(
"knife_count": len(knife_items)
})
-@app.get("/inventory", response_class=HTMLResponse)
-async def inventory_page(
- request: Request,
- db: Session = Depends(get_db),
- user: User = Depends(get_current_user)
-):
- inventory_items = db.query(InventoryItem).filter(
- InventoryItem.user_id == user.id
- ).order_by(desc(InventoryItem.obtained_at)).all()
-
- rarities = set()
- types = set()
-
- enriched_items = []
- for item in inventory_items:
- # Ищем предмет по item_id
- item_data = None
- for idx, it in enumerate(ALL_ITEMS):
- if it.get("_id", idx) == item.item_id:
- item_data = it
- break
-
- # Если не нашли, пробуем get_item
- if not item_data:
- item_data = get_item(item.item_id)
-
- image_url = ""
- price_rub = 100
-
- if item_data:
- image_url = item_data.get("image_url", "")
- price_rub = item_data.get("price_rub", 100)
-
- enriched_items.append({
- "id": item.id,
- "item_id": item.item_id,
- "name": item.market_hash_name,
- "rarity": item.rarity,
- "wear": item.wear,
- "float": item.float_value,
- "type": item.type,
- "obtained_from": item.obtained_from,
- "obtained_at": item.obtained_at,
- "image_url": image_url,
- "price_rub": price_rub
- })
-
- if item.rarity:
- rarities.add(item.rarity)
- if item.type:
- types.add(item.type)
-
- return templates.TemplateResponse("inventory.html", {
- "request": request,
- "user": user,
- "inventory_items": enriched_items,
- "rarities": sorted(rarities),
- "types": sorted(types),
- "rarity_order": RARITY_ORDER
- })
+@app.get("/inventory")
+async def inventory_redirect():
+ return RedirectResponse(url="/profile")
# ─── API эндпоинты для веб-интерфейса ─────────────────────────────────────
diff --git a/static/css/style.css b/static/css/style.css
index 9137fdc..786e8c8 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -4486,4 +4486,218 @@ a:not(.btn):not(.nav-link):not(.logo) {
}
a:not(.btn):not(.nav-link):not(.logo):hover {
border-bottom-color: var(--primary-color);
+}
+
+/* =============================================
+ 🍞 Уведомления (Toast + Confirm)
+ ============================================= */
+
+.toast-container {
+ position: fixed;
+ top: 72px;
+ right: 16px;
+ z-index: 9999;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ pointer-events: none;
+ max-width: 380px;
+ width: 100%;
+}
+
+.toast {
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-light);
+ border-radius: 10px;
+ padding: 12px 16px;
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ pointer-events: auto;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
+ animation: toastSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+ transform-origin: top right;
+ position: relative;
+ overflow: hidden;
+}
+
+.toast::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 3px;
+ border-radius: 3px 0 0 3px;
+}
+
+.toast-success::before { background: var(--success); }
+.toast-error::before { background: var(--danger); }
+.toast-info::before { background: var(--primary); }
+.toast-warning::before { background: var(--warning); }
+
+.toast-icon {
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ border-radius: 6px;
+ font-size: 0.85rem;
+}
+
+.toast-success .toast-icon { background: rgba(16,185,129,0.15); }
+.toast-error .toast-icon { background: rgba(239,68,68,0.15); }
+.toast-info .toast-icon { background: rgba(245,158,11,0.15); }
+.toast-warning .toast-icon { background: rgba(245,158,11,0.15); }
+
+.toast-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.toast-title {
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: var(--text);
+ line-height: 1.3;
+}
+
+.toast-message {
+ font-size: 0.75rem;
+ color: var(--text-dim);
+ margin-top: 2px;
+ line-height: 1.3;
+}
+
+.toast-close {
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: none;
+ color: var(--text-muted);
+ cursor: pointer;
+ font-size: 0.75rem;
+ flex-shrink: 0;
+ border-radius: 4px;
+ transition: all 0.15s;
+}
+
+.toast-close:hover {
+ background: rgba(255,255,255,0.06);
+ color: var(--text);
+}
+
+.toast-removing {
+ animation: toastSlideOut 0.25s cubic-bezier(0.4, 0, 1, 1) forwards;
+}
+
+@keyframes toastSlideIn {
+ from { opacity: 0; transform: translateX(100%) scale(0.9); }
+ to { opacity: 1; transform: translateX(0) scale(1); }
+}
+
+@keyframes toastSlideOut {
+ from { opacity: 1; transform: translateX(0) scale(1); }
+ to { opacity: 0; transform: translateX(100%) scale(0.9); }
+}
+
+/* =============================================
+ ⚠️ Confirm диалог
+ ============================================= */
+
+.confirm-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.7);
+ backdrop-filter: blur(4px);
+ -webkit-backdrop-filter: blur(4px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10000;
+ animation: confirmFadeIn 0.2s ease;
+}
+
+.confirm-dialog {
+ background: var(--bg-card);
+ border: 1px solid var(--border-light);
+ border-radius: 14px;
+ padding: 24px;
+ max-width: 400px;
+ width: 90%;
+ animation: confirmScaleIn 0.25s cubic-bezier(0.16, 1, 0.3, 1);
+ box-shadow: 0 24px 80px rgba(0,0,0,0.6);
+}
+
+.confirm-dialog-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 12px;
+}
+
+.confirm-dialog-icon {
+ width: 36px;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 10px;
+ font-size: 1.1rem;
+ flex-shrink: 0;
+}
+
+.confirm-dialog-icon-warning { background: rgba(239,68,68,0.15); }
+.confirm-dialog-icon-info { background: rgba(245,158,11,0.15); }
+
+.confirm-dialog-title {
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--text);
+}
+
+.confirm-dialog-message {
+ font-size: 0.88rem;
+ color: var(--text-dim);
+ line-height: 1.5;
+ margin-bottom: 20px;
+ padding-left: 46px;
+}
+
+.confirm-dialog-actions {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+}
+
+.confirm-dialog-actions .btn {
+ min-width: 90px;
+ padding: 0.55rem 1.2rem;
+ font-size: 0.8rem;
+}
+
+.confirm-dialog .btn-danger {
+ background: var(--danger);
+ color: white;
+ border: none;
+}
+
+.confirm-dialog .btn-danger:hover {
+ background: #dc2626;
+ box-shadow: 0 4px 16px rgba(239,68,68,0.3);
+}
+
+@keyframes confirmFadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes confirmScaleIn {
+ from { opacity: 0; transform: scale(0.9) translateY(10px); }
+ to { opacity: 1; transform: scale(1) translateY(0); }
}
\ No newline at end of file
diff --git a/static/js/notifications.js b/static/js/notifications.js
new file mode 100644
index 0000000..89fd6aa
--- /dev/null
+++ b/static/js/notifications.js
@@ -0,0 +1,137 @@
+(function() {
+ 'use strict';
+
+ let toastId = 0;
+
+ const container = document.createElement('div');
+ container.className = 'toast-container';
+ container.id = 'toastContainer';
+ document.body.appendChild(container);
+
+ function escapeHtml(text) {
+ const d = document.createElement('div');
+ d.textContent = text || '';
+ return d.innerHTML;
+ }
+
+ window.Notify = {
+ toast: function(message, type, title) {
+ if (!message) return;
+ type = type || 'info';
+ const icons = { success: '✓', error: '✕', info: 'ℹ', warning: '⚠' };
+ const titles = { success: title || 'Успешно', error: title || 'Ошибка', info: title || 'Информация', warning: title || 'Внимание' };
+ const icon = icons[type] || 'ℹ';
+ const t = titles[type];
+
+ const id = ++toastId;
+ const el = document.createElement('div');
+ el.className = 'toast toast-' + type;
+ el.id = 'toast-' + id;
+ el.innerHTML =
+ '
' + icon + '
' +
+ '' +
+ '
' + escapeHtml(t) + '
' +
+ '
' + escapeHtml(message) + '
' +
+ '
' +
+ '';
+
+ container.appendChild(el);
+
+ const timeout = setTimeout(function() {
+ Notify.dismiss(id);
+ }, 4000);
+
+ el._timeout = timeout;
+ el._id = id;
+
+ return id;
+ },
+
+ success: function(message) {
+ return this.toast(message, 'success');
+ },
+
+ error: function(message) {
+ return this.toast(message, 'error');
+ },
+
+ info: function(message) {
+ return this.toast(message, 'info');
+ },
+
+ warning: function(message) {
+ return this.toast(message, 'warning');
+ },
+
+ dismiss: function(id) {
+ var el = document.getElementById('toast-' + id);
+ if (!el) return;
+ if (el._timeout) clearTimeout(el._timeout);
+ if (el.classList.contains('toast-removing')) return;
+ el.classList.add('toast-removing');
+ setTimeout(function() {
+ if (el.parentNode) el.parentNode.removeChild(el);
+ }, 250);
+ },
+
+ confirm: function(message, title, callback) {
+ if (typeof title === 'function') {
+ callback = title;
+ title = 'Подтверждение';
+ }
+ title = title || 'Подтверждение';
+
+ var overlay = document.createElement('div');
+ overlay.className = 'confirm-overlay';
+ overlay.innerHTML =
+ '' +
+ '' +
+ '
' + escapeHtml(message) + '
' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '
';
+
+ document.body.appendChild(overlay);
+
+ var result = false;
+
+ function close() {
+ if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
+ if (callback) callback(result);
+ }
+
+ overlay.querySelector('.confirm-cancel').addEventListener('click', function() {
+ result = false;
+ close();
+ });
+
+ overlay.querySelector('.confirm-ok').addEventListener('click', function() {
+ result = true;
+ close();
+ });
+
+ overlay.addEventListener('click', function(e) {
+ if (e.target === overlay) {
+ result = false;
+ close();
+ }
+ });
+
+ document.addEventListener('keydown', function handler(e) {
+ if (e.key === 'Escape') {
+ result = false;
+ close();
+ document.removeEventListener('keydown', handler);
+ }
+ });
+
+ return overlay;
+ }
+ };
+
+})();
diff --git a/templates/_activity_sidebar.html b/templates/_activity_sidebar.html
index 484c579..620320d 100644
--- a/templates/_activity_sidebar.html
+++ b/templates/_activity_sidebar.html
@@ -184,19 +184,27 @@ document.addEventListener('DOMContentLoaded', async () => {
document.getElementById('activityToggleBtn').classList.add('visible');
}
+ async function loadActivities() {
+ try {
+ const res = await fetch('/web/api/activity?limit=10');
+ const data = await res.json();
+ if (data.success && data.activities) {
+ renderActivities(data.activities);
+ }
+ } catch (e) {
+ if (list) list.innerHTML = 'Не удалось загрузить
';
+ }
+ }
+
WS.on('online_count', (data) => {
document.getElementById('onlineCount').textContent = data.online || 0;
});
- try {
- const res = await fetch('/web/api/activity?limit=10');
- const data = await res.json();
- if (data.success && data.activities) {
- renderActivities(data.activities);
- }
- } catch (e) {
- if (list) list.innerHTML = 'Не удалось загрузить
';
- }
+ WS.on('connected', () => {
+ loadActivities();
+ });
+
+ await loadActivities();
WS.on('activity', (data) => {
const act = data.activity;
@@ -210,6 +218,12 @@ document.addEventListener('DOMContentLoaded', async () => {
list.removeChild(list.lastChild);
}
});
+
+ // Periodic refresh every 30s to catch missed activities
+ setInterval(loadActivities, 30000);
+
+ // Expose for manual refresh from other pages
+ window.refreshActivityFeed = loadActivities;
});
const RARITY_COLORS = {
diff --git a/templates/achievements.html b/templates/achievements.html
index ba1e16a..4c333d5 100644
--- a/templates/achievements.html
+++ b/templates/achievements.html
@@ -191,7 +191,7 @@
Контракты
🆙 Апгрейд
💥 Crash
- Инвентарь
+ Профиль
🏆 Достижения
📰 Лента
Профиль
@@ -272,6 +272,7 @@
+
+
+
{% endblock %}
diff --git a/templates/admin/user_detail.html b/templates/admin/user_detail.html
index dc898db..7f40753 100644
--- a/templates/admin/user_detail.html
+++ b/templates/admin/user_detail.html
@@ -172,6 +172,7 @@ let currentRPU = null;
function closeModal(id) { document.getElementById(id).classList.remove('open'); }
function openModal(id) { document.getElementById(id).classList.add('open'); }
+
// ====== BALANCE ======
function openBalanceModal() { openModal('balanceModal'); }
async function updateBalance() {
@@ -180,8 +181,8 @@ async function updateBalance() {
fd.append('operation', document.getElementById('balanceOperation').value);
const res = await fetch(`/admin/api/user/${userId}/balance`, { method:'POST', body:fd });
const d = await res.json();
- if (d.success) { alert(d.message); window.location.reload(); }
- else alert(d.error);
+ if (d.success) { Notify.success(d.message); window.location.reload(); }
+ else Notify.error(d.error);
}
// ====== GIVE ITEM ======
@@ -205,31 +206,37 @@ async function giveItem(itemId) {
const fd = new FormData(); fd.append('item_id', itemId);
const res = await fetch(`/admin/api/user/${userId}/give-item`, { method:'POST', body:fd });
const d = await res.json();
- if (d.success) { alert(d.message); closeModal('giveItemModal'); window.location.reload(); }
- else alert(d.error);
+ if (d.success) { Notify.success(d.message); closeModal('giveItemModal'); window.location.reload(); }
+ else Notify.error(d.error);
}
// ====== DELETE ITEM ======
async function deleteItem(invId) {
- if (!confirm('Удалить предмет?')) return;
- const res = await fetch(`/admin/api/user/${userId}/delete-item/${invId}`, { method:'POST' });
- const d = await res.json();
- if (d.success) window.location.reload();
- else alert(d.error);
+ Notify.confirm('Удалить предмет?', 'Подтверждение', async function(ok) {
+ if (!ok) return;
+ const res = await fetch(`/admin/api/user/${userId}/delete-item/${invId}`, { method:'POST' });
+ const d = await res.json();
+ if (d.success) window.location.reload();
+ else Notify.error(d.error);
+ });
}
// ====== TOGGLE ADMIN / BAN ======
async function toggleAdmin() {
- if (!confirm('Изменить статус админа?')) return;
- const res = await fetch(`/admin/api/user/${userId}/toggle-admin`, { method:'POST' });
- const d = await res.json();
- if (d.success) window.location.reload(); else alert(d.error);
+ Notify.confirm('Изменить статус админа?', 'Подтверждение', async function(ok) {
+ if (!ok) return;
+ const res = await fetch(`/admin/api/user/${userId}/toggle-admin`, { method:'POST' });
+ const d = await res.json();
+ if (d.success) window.location.reload(); else Notify.error(d.error);
+ });
}
async function toggleBan() {
- if (!confirm('{{ "Забанить" if not target_user.is_banned else "Разбанить" }}?')) return;
- const res = await fetch(`/admin/api/user/${userId}/toggle-ban`, { method:'POST' });
- const d = await res.json();
- if (d.success) window.location.reload(); else alert(d.error);
+ Notify.confirm('{{ "Забанить" if not target_user.is_banned else "Разбанить" }}?', 'Подтверждение', async function(ok) {
+ if (!ok) return;
+ const res = await fetch(`/admin/api/user/${userId}/toggle-ban`, { method:'POST' });
+ const d = await res.json();
+ if (d.success) window.location.reload(); else Notify.error(d.error);
+ });
}
// ====== RPU ======
@@ -277,16 +284,18 @@ async function saveRPU() {
fd.append('auto_adjust', document.getElementById('rpuAutoAdjust').checked);
const res = await fetch(`/admin/api/user/${userId}/rpu/update`, { method:'POST', body:fd });
const d = await res.json();
- if (d.success) { alert(d.message); closeModal('rpuModal'); loadRPU(); }
- else alert(d.error);
+ if (d.success) { Notify.success(d.message); closeModal('rpuModal'); loadRPU(); }
+ else Notify.error(d.error);
}
async function setRPUPreset(preset) {
const names = { lucky:'🍀 Везучий', normal:'📊 Обычный', unlucky:'🌧️ Невезучий', very_unlucky:'💀 Проклятый' };
- if (!confirm('Применить "'+names[preset]+'"?')) return;
- const fd = new FormData(); fd.append('preset', preset);
- const res = await fetch(`/admin/api/user/${userId}/rpu/preset`, { method:'POST', body:fd });
- const d = await res.json();
- if (d.success) { alert(d.message); loadRPU(); } else alert(d.error);
+ Notify.confirm('Применить "'+names[preset]+'"?', 'Подтверждение', async function(ok) {
+ if (!ok) return;
+ const fd = new FormData(); fd.append('preset', preset);
+ const res = await fetch(`/admin/api/user/${userId}/rpu/preset`, { method:'POST', body:fd });
+ const d = await res.json();
+ if (d.success) { Notify.success(d.message); loadRPU(); } else Notify.error(d.error);
+ });
}
document.addEventListener('DOMContentLoaded', loadRPU);
diff --git a/templates/case_detail.html b/templates/case_detail.html
index f73e080..d38902f 100644
--- a/templates/case_detail.html
+++ b/templates/case_detail.html
@@ -223,7 +223,7 @@
Контракты
🆙 Апгрейд
💥 Crash
- Инвентарь
+ Профиль
📰 Лента
🏆 Достижения
Профиль
@@ -603,7 +603,10 @@
const hasFreeSpin = freeSpinActive;
freeSpinActive = false;
if (!hasFreeSpin && userBalance < totalPrice) {
- alert(`Недостаточно средств!\nНужно: ${totalPrice.toLocaleString()} ₽\nУ вас: ${Math.floor(userBalance).toLocaleString()} ₽`);
+ Notify.error(`Недостаточно средств! Нужно: ${totalPrice.toLocaleString()} ₽, у вас: ${Math.floor(userBalance).toLocaleString()} ₽`);
+ isOpening = false;
+ openButton.disabled = false;
+ openButton.textContent = isSlotCase ? '🎰 Крутить' : '🎲 Открыть';
return;
}
isOpening = true;
@@ -658,7 +661,7 @@
if (data.new_balance !== undefined) userBalance = data.new_balance;
await refreshUserBalance();
} else {
- alert(data.error || 'Ошибка при открытии');
+ Notify.error(data.error || 'Ошибка при открытии');
}
} else if (isFarmCase) {
// ===== ФАРМ-КЕЙС: КАРТОЧКИ С АВТОПРОДАЖЕЙ ШЛАКА =====
@@ -697,7 +700,7 @@
}
await refreshUserBalance();
} else {
- alert(data.error || 'Ошибка при открытии');
+ Notify.error(data.error || 'Ошибка при открытии');
}
} else if (fast) {
// ===== БЫСТРОЕ ОТКРЫТИЕ БЕЗ АНИМАЦИИ =====
@@ -720,7 +723,7 @@
if (data.new_balance !== undefined) userBalance = data.new_balance;
await refreshUserBalance();
} else {
- alert(data.error || 'Ошибка при открытии');
+ Notify.error(data.error || 'Ошибка при открытии');
}
} else {
// ===== ОБЫЧНЫЙ КЕЙС: СКРОЛЛЫ (РУЛЕТКА) =====
@@ -763,7 +766,7 @@
})();
if (!batchData.success) {
- alert(batchData.error || 'Ошибка при открытии');
+ Notify.error(batchData.error || 'Ошибка при открытии');
} else {
handleAchievements(batchData);
SoundManager.caseOpen();
@@ -797,7 +800,7 @@
}
} catch (error) {
console.error('Error opening cases:', error);
- alert('Ошибка при открытии кейсов');
+ Notify.error('Ошибка при открытии кейсов');
} finally {
isOpening = false;
openButton.disabled = false;
@@ -1338,43 +1341,44 @@
async function sellAllTopDrops() {
if (topDropItems.length === 0) {
- alert('Нет предметов для продажи');
+ Notify.error('Нет предметов для продажи');
return;
}
const total = topDropItems.reduce((sum, i) => sum + (i.price || 100), 0);
- if (!confirm(`Продать все топ-дропы за ${total.toLocaleString()} ₽?`)) return;
-
- try {
- const response = await fetch('/web/api/inventory/items');
- const items = await response.json();
-
- const inventoryIds = [];
- for (const item of topDropItems) {
- const invItem = items.find(i => i.item_id === item.id &&
- Math.abs(i.float - item.float) < 0.001);
- if (invItem) {
- inventoryIds.push(invItem.id);
- }
- }
-
- if (inventoryIds.length > 0) {
- const formData = new FormData();
- formData.append('inventory_ids', JSON.stringify(inventoryIds));
+ Notify.confirm('Продать все топ-дропы за ' + total.toLocaleString() + ' ₽?', 'Продажа', async function(confirmed) {
+ if (!confirmed) return;
+ try {
+ const response = await fetch('/web/api/inventory/items');
+ const items = await response.json();
- await fetch('/web/api/inventory/sell/bulk', {
- method: 'POST',
- body: formData
- });
+ const inventoryIds = [];
+ for (const item of topDropItems) {
+ const invItem = items.find(i => i.item_id === item.id &&
+ Math.abs(i.float - item.float) < 0.001);
+ if (invItem) {
+ inventoryIds.push(invItem.id);
+ }
+ }
+
+ if (inventoryIds.length > 0) {
+ const formData = new FormData();
+ formData.append('inventory_ids', JSON.stringify(inventoryIds));
+
+ await fetch('/web/api/inventory/sell/bulk', {
+ method: 'POST',
+ body: formData
+ });
+ }
+
+ await refreshUserBalance();
+ await refreshInventoryAjax();
+ topDropItems = [];
+ resetCaseUI();
+ } catch (error) {
+ Notify.error('Ошибка соединения');
}
-
- await refreshUserBalance();
- await refreshInventoryAjax();
- topDropItems = [];
- resetCaseUI();
- } catch (error) {
- alert('Ошибка соединения');
- }
+ });
}
// ===== ЭФФЕКТЫ =====
@@ -1749,102 +1753,104 @@
}
async function sellResultItem(itemId) {
- if (!confirm('Продать этот предмет?')) return;
-
- try {
- const response = await fetch('/web/api/inventory/items');
- const items = await response.json();
- const inventoryItem = items.find(i => i.item_id === itemId);
-
- if (!inventoryItem) {
- alert('Предмет не найден в инвентаре');
- return;
- }
-
- const formData = new FormData();
- formData.append('inventory_id', inventoryItem.id);
-
- const sellResponse = await fetch('/web/api/inventory/sell', {
- method: 'POST',
- body: formData
- });
-
- const data = await sellResponse.json();
-
- if (data.success) {
- await refreshUserBalance();
- await refreshInventoryAjax();
- // Remove the sold item card from display
- const cards = document.querySelectorAll('.result-card-enhanced');
- for (const card of cards) {
- const btn = card.querySelector('.btn-sell-result');
- if (btn && btn.getAttribute('onclick')?.includes(itemId)) {
- card.style.transition = 'all 0.3s ease';
- card.style.opacity = '0';
- card.style.transform = 'scale(0.8)';
- setTimeout(() => card.remove(), 300);
- break;
- }
+ Notify.confirm('Продать этот предмет?', 'Продажа', async function(confirmed) {
+ if (!confirmed) return;
+ try {
+ const response = await fetch('/web/api/inventory/items');
+ const items = await response.json();
+ const inventoryItem = items.find(i => i.item_id === itemId);
+
+ if (!inventoryItem) {
+ Notify.error('Предмет не найден в инвентаре');
+ return;
}
- } else {
- alert(data.error || 'Ошибка при продаже');
+
+ const formData = new FormData();
+ formData.append('inventory_id', inventoryItem.id);
+
+ const sellResponse = await fetch('/web/api/inventory/sell', {
+ method: 'POST',
+ body: formData
+ });
+
+ const data = await sellResponse.json();
+
+ if (data.success) {
+ Notify.success('Предмет продан за ' + data.sold_price.toFixed(0) + ' ₽');
+ await refreshUserBalance();
+ await refreshInventoryAjax();
+ const cards = document.querySelectorAll('.result-card-enhanced');
+ for (const card of cards) {
+ const btn = card.querySelector('.btn-sell-result');
+ if (btn && btn.getAttribute('onclick')?.includes(itemId)) {
+ card.style.transition = 'all 0.3s ease';
+ card.style.opacity = '0';
+ card.style.transform = 'scale(0.8)';
+ setTimeout(() => card.remove(), 300);
+ break;
+ }
+ }
+ } else {
+ Notify.error(data.error || 'Ошибка при продаже');
+ }
+ } catch (error) {
+ Notify.error('Ошибка соединения');
}
- } catch (error) {
- alert('Ошибка соединения');
- }
+ });
}
async function sellAllResults() {
if (!window.lastOpenedItems || window.lastOpenedItems.length === 0) {
- alert('Нет предметов для продажи');
+ Notify.error('Нет предметов для продажи');
return;
}
const total = window.lastOpenedItems.reduce((sum, i) => sum + (i.price || 100), 0);
- if (!confirm(`Продать все выпавшие предметы за ${total.toLocaleString()} ₽?`)) return;
-
- try {
- const response = await fetch('/web/api/inventory/items');
- const items = await response.json();
-
- const inventoryIds = [];
- for (const resultItem of window.lastOpenedItems) {
- const invItem = items.find(i => i.item_id === resultItem.id &&
- Math.abs(i.float - resultItem.float) < 0.001);
- if (invItem) {
- inventoryIds.push(invItem.id);
+ Notify.confirm('Продать все выпавшие предметы за ' + total.toLocaleString() + ' ₽?', 'Продажа', async function(confirmed) {
+ if (!confirmed) return;
+ try {
+ const response = await fetch('/web/api/inventory/items');
+ const items = await response.json();
+
+ const inventoryIds = [];
+ for (const resultItem of window.lastOpenedItems) {
+ const invItem = items.find(i => i.item_id === resultItem.id &&
+ Math.abs(i.float - resultItem.float) < 0.001);
+ if (invItem) {
+ inventoryIds.push(invItem.id);
+ }
}
- }
-
- if (inventoryIds.length === 0) {
- alert('Предметы не найдены в инвентаре');
- return;
- }
-
- const formData = new FormData();
- formData.append('inventory_ids', JSON.stringify(inventoryIds));
-
- const sellResponse = await fetch('/web/api/inventory/sell/bulk', {
- method: 'POST',
- body: formData
- });
-
- const data = await sellResponse.json();
+
+ if (inventoryIds.length === 0) {
+ Notify.error('Предметы не найдены в инвентаре');
+ return;
+ }
+
+ const formData = new FormData();
+ formData.append('inventory_ids', JSON.stringify(inventoryIds));
+
+ const sellResponse = await fetch('/web/api/inventory/sell/bulk', {
+ method: 'POST',
+ body: formData
+ });
+
+ const data = await sellResponse.json();
- if (data.success) {
- await refreshUserBalance();
- await refreshInventoryAjax();
- // Clear results and reset UI
- const resultsDiv = document.getElementById('openingResults');
- resultsDiv.style.display = 'none';
- resultsDiv.innerHTML = '';
- window.lastOpenedItems = [];
- } else {
- alert(data.error || 'Ошибка при продаже');
+ if (data.success) {
+ Notify.success('Продано ' + data.sold_count + ' предметов за ' + data.total_received.toFixed(0) + ' ₽');
+ await refreshUserBalance();
+ await refreshInventoryAjax();
+ const resultsDiv = document.getElementById('openingResults');
+ resultsDiv.style.display = 'none';
+ resultsDiv.innerHTML = '';
+ window.lastOpenedItems = [];
+ } else {
+ Notify.error(data.error || 'Ошибка при продаже');
+ }
+ } catch (error) {
+ Notify.error('Ошибка соединения');
}
- } catch (error) {
- alert('Ошибка соединения');
- }
+ });
}
function filterItems(rarity) {
@@ -1879,6 +1885,7 @@
+
+
+
{% if user %}{% include '_activity_sidebar.html' %}{% endif %}