diff --git a/src/web/managers/inventory.py b/src/web/managers/inventory.py
index c736bef..6856942 100644
--- a/src/web/managers/inventory.py
+++ b/src/web/managers/inventory.py
@@ -3,6 +3,7 @@
from __future__ import annotations
import asyncio
+import logging
from typing import TYPE_CHECKING, Any
@@ -12,6 +13,9 @@ if TYPE_CHECKING:
from src.web.managers.cache import ImageCache
+logger = logging.getLogger("TwitchDrops")
+
+
class InventoryManager:
"""Manages drop campaign inventory display in the web interface.
@@ -41,6 +45,16 @@ class InventoryManager:
drops_data = []
for drop in campaign.drops:
+ # Collect full benefit data (filter out benefits without images)
+ benefits_data = [
+ {
+ "name": benefit.name,
+ "type": benefit.type.name,
+ "image_url": str(benefit.image_url),
+ }
+ for benefit in drop.benefits
+ if benefit.image_url
+ ]
drops_data.append(
{
"id": drop.id,
@@ -50,7 +64,7 @@ class InventoryManager:
"progress": drop.progress,
"is_claimed": drop.is_claimed,
"can_claim": drop.can_claim,
- "rewards": drop.rewards_text(),
+ "benefits": benefits_data,
"starts_at": drop.starts_at.isoformat(),
"ends_at": drop.ends_at.isoformat(),
}
diff --git a/web/static/app.js b/web/static/app.js
index 19eef05..fd7f0ba 100644
--- a/web/static/app.js
+++ b/web/static/app.js
@@ -494,14 +494,36 @@ function renderInventory() {
}
const claimedText = t.gui?.inventory?.status?.claimed || 'Claimed';
- const dropsHtml = campaign.drops.map(drop => `
-
-
${drop.name}
-
${drop.rewards}
-
${drop.current_minutes} / ${drop.required_minutes} minutes (${Math.round(drop.progress * 100)}%)
- ${drop.is_claimed ? `
✓ ${claimedText}
` : ''}
-
- `).join('');
+ const dropsHtml = campaign.drops.map(drop => {
+ // Generate HTML for each benefit as its own line
+ let benefitsHtml = '';
+ if (drop.benefits && drop.benefits.length > 0) {
+ benefitsHtml = drop.benefits.map(benefit =>
+ `
+

+
+ ${benefit.name}
+ (${benefit.type})
+
+
`
+ ).join('');
+ }
+
+ return `
+
+
+
+ ${benefitsHtml}
+
+
${drop.current_minutes} / ${drop.required_minutes} minutes (${Math.round(drop.progress * 100)}%)
+ ${drop.is_claimed ? `
✓ ${claimedText}
` : ''}
+
+ `;
+ }).join('');
// Make campaign name clickable if link_url is available
const campaignNameHtml = campaign.link_url
diff --git a/web/static/styles.css b/web/static/styles.css
index 9fec853..a9369b3 100644
--- a/web/static/styles.css
+++ b/web/static/styles.css
@@ -500,6 +500,66 @@ header h1 {
border-left-color: var(--accent-color);
}
+.drop-item-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 8px;
+}
+
+.drop-item-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.benefits-list {
+ margin-top: 8px;
+ margin-bottom: 8px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.benefit-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px;
+ background: var(--bg-panel);
+ border-radius: 4px;
+ border: 1px solid var(--border-color);
+}
+
+.benefit-icon {
+ width: 40px;
+ height: 40px;
+ border-radius: 4px;
+ object-fit: cover;
+ border: 2px solid var(--border-color);
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ flex-shrink: 0;
+}
+
+.benefit-info {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ min-width: 0;
+}
+
+.benefit-name {
+ font-weight: 500;
+ font-size: 14px;
+ color: var(--text-primary);
+}
+
+.benefit-type {
+ font-size: 12px;
+ color: var(--text-secondary);
+ font-style: italic;
+}
+
/* Settings */
.settings-container {
max-width: 800px;