From 8dce2fd1610e3c0f29f0a1eaefc1d7547e9e4136 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 8 Nov 2025 22:08:21 +1100 Subject: [PATCH] feat: add account linking status badges to inventory campaign cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add visual indicators showing account linking status for each campaign in the inventory tab. Campaigns now display either a "LINKED" (green) or "NOT LINKED" (orange) badge in the top right corner of each card. For unlinked campaigns, both the badge and a "Link Account" button provide direct access to the Twitch account linking page. Backend changes: - Add campaign_url property to DropsCampaign model - Include both campaign_url and link_url in inventory API response Frontend changes: - Add LINKED/NOT LINKED badges positioned at top right of campaign cards - Add "Link Account" button for unlinked campaigns - Style badges with subtle green for linked and prominent orange for not linked - Make NOT LINKED badge clickable to open account linking page 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/models/campaign.py | 1 + src/web/managers/inventory.py | 3 +- web/static/app.js | 20 +++++++++--- web/static/styles.css | 58 +++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/models/campaign.py b/src/models/campaign.py index 4d4dcb4..2c68a9e 100644 --- a/src/models/campaign.py +++ b/src/models/campaign.py @@ -28,6 +28,7 @@ class DropsCampaign: def __init__(self, twitch: Twitch, data: JsonType, claimed_benefits: dict[str, datetime]): self._twitch: Twitch = twitch self.id: str = data["id"] + self.campaign_url: str = f"https://www.twitch.tv/drops/campaigns?dropID={self.id}" self.name: str = data["name"] self.game: Game = Game(data["game"]) self.linked: bool = data["self"]["isAccountConnected"] diff --git a/src/web/managers/inventory.py b/src/web/managers/inventory.py index 6856942..1dd86a2 100644 --- a/src/web/managers/inventory.py +++ b/src/web/managers/inventory.py @@ -75,7 +75,8 @@ class InventoryManager: "name": campaign.name, "game_name": campaign.game.name, "image_url": image_url, - "link_url": f"https://www.twitch.tv/drops/campaigns?dropID={campaign.id}", + "campaign_url": campaign.campaign_url, + "link_url": campaign.link_url, "starts_at": campaign.starts_at.isoformat(), "ends_at": campaign.ends_at.isoformat(), "linked": campaign.linked, diff --git a/web/static/app.js b/web/static/app.js index ed9d9cb..4fe7e48 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -800,16 +800,26 @@ function renderInventory() { `; }).join(''); - // Make campaign name clickable if link_url is available - const campaignNameHtml = campaign.link_url - ? `${campaign.name} 🔗` - : `
${campaign.name}
`; + const campaignNameHtml = `${campaign.name} 🔗` + + // Add LINKED or NOT LINKED badge + const linkStatusBadgeHtml = campaign.linked + ? `LINKED` + : `NOT LINKED`; + + const linkAccountButtonHtml = !campaign.linked && campaign.link_url + ? `` + : ''; const claimedCountText = t.gui?.inventory?.claimed_drops || 'claimed'; card.innerHTML = `
-
${campaign.game_name}
+
+ ${campaign.game_name} + ${linkStatusBadgeHtml} +
${campaignNameHtml} + ${linkAccountButtonHtml}
${statusText} diff --git a/web/static/styles.css b/web/static/styles.css index 843f54f..f575dfc 100644 --- a/web/static/styles.css +++ b/web/static/styles.css @@ -609,6 +609,7 @@ header h1 { border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); + position: relative; } .campaign-header { @@ -647,6 +648,63 @@ header h1 { opacity: 0.7; } +.campaign-badge { + position: absolute; + right: 15px; + padding: 4px 10px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + z-index: 10; +} + +.campaign-badge.linked { + background: rgba(0, 200, 83, 0.15); + color: var(--success-color); + border: 1px solid rgba(0, 200, 83, 0.3); + cursor: default; +} + +.campaign-badge.not-linked { + background: var(--warning-color); + color: white; + box-shadow: 0 2px 4px rgba(255, 167, 38, 0.3); +} + +.campaign-badge.not-linked:hover { + background: #ff9800; + transform: translateY(-1px); + box-shadow: 0 3px 6px rgba(255, 167, 38, 0.4); +} + +.link-account-btn { + margin-top: 8px; + padding: 8px 16px; + background: var(--warning-color); + color: white; + border: none; + border-radius: 6px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + box-shadow: 0 2px 4px rgba(255, 167, 38, 0.3); + width: 100%; +} + +.link-account-btn:hover { + background: #ff9800; + transform: translateY(-1px); + box-shadow: 0 3px 6px rgba(255, 167, 38, 0.4); +} + +.link-account-btn:active { + transform: translateY(0); + box-shadow: 0 1px 2px rgba(255, 167, 38, 0.3); +} + .campaign-status { padding: 10px 15px; font-size: 12px;