feat: add account linking status badges to inventory campaign cards

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 <noreply@anthropic.com>
This commit is contained in:
github-actions[bot]
2025-11-08 22:08:21 +11:00
parent 225b74467e
commit 8dce2fd161
4 changed files with 76 additions and 6 deletions

View File

@@ -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"]

View File

@@ -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,

View File

@@ -800,16 +800,26 @@ function renderInventory() {
`;
}).join('');
// Make campaign name clickable if link_url is available
const campaignNameHtml = campaign.link_url
? `<a href="${campaign.link_url}" target="_blank" rel="noopener noreferrer" class="campaign-name-link">${campaign.name} <span class="external-link-icon">🔗</span></a>`
: `<div class="campaign-name">${campaign.name}</div>`;
const campaignNameHtml = `<a href="${campaign.campaign_url}" target="_blank" rel="noopener noreferrer" class="campaign-name-link">${campaign.name} <span class="external-link-icon">🔗</span></a>`
// Add LINKED or NOT LINKED badge
const linkStatusBadgeHtml = campaign.linked
? `<span class="campaign-badge linked" title="Account is linked">LINKED</span>`
: `<span class="campaign-badge not-linked" onclick="window.open('${campaign.link_url}', '_blank')" title="Click to link your account">NOT LINKED</span>`;
const linkAccountButtonHtml = !campaign.linked && campaign.link_url
? `<button class="link-account-btn" onclick="window.open('${campaign.link_url}', '_blank')">Link Account</button>`
: '';
const claimedCountText = t.gui?.inventory?.claimed_drops || 'claimed';
card.innerHTML = `
<div class="campaign-header">
<div class="campaign-game">${campaign.game_name}</div>
<div class="campaign-game">
${campaign.game_name}
${linkStatusBadgeHtml}
</div>
${campaignNameHtml}
${linkAccountButtonHtml}
</div>
<div class="campaign-status">
<span>${statusText}</span>

View File

@@ -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;