feat: add tier badges to marketplace UI

- Show 'core' (orange) and 'recommended' (blue) badges next to app titles
- getAppTier() classifies apps matching backend get_app_tier()
- Global .tier-badge, .tier-badge-core, .tier-badge-recommended CSS classes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-03-14 03:35:09 +00:00
parent ee825cd8d6
commit 8f925a99f7
2 changed files with 48 additions and 9 deletions

View File

@ -123,7 +123,7 @@ input[type="radio"]:active + * {
/* Mode switcher - sidebar toggle */
.mode-switcher {
display: flex;
display: inline-flex;
gap: 2px;
padding: 3px;
border-radius: 0.5rem;
@ -132,9 +132,10 @@ input[type="radio"]:active + * {
}
.mode-switcher-btn {
flex: 1;
padding: 0.375rem 0.5rem;
flex: none;
padding: 0.375rem 0.75rem;
border-radius: 0.375rem;
white-space: nowrap;
font-size: 0.75rem;
font-weight: 500;
color: rgba(255, 255, 255, 0.45);
@ -214,15 +215,15 @@ input[type="radio"]:active + * {
}
.chat-mode-pill {
display: none;
position: absolute;
top: calc(env(safe-area-inset-top, 0px) + 2.25rem);
right: calc(env(safe-area-inset-right, 0px) + 1.25rem);
top: 2.25rem;
right: 1.25rem;
z-index: 10;
}
@media (min-width: 768px) {
.chat-mode-pill {
top: 2.25rem;
right: 1.25rem;
display: flex;
}
}
@ -237,7 +238,7 @@ input[type="radio"]:active + * {
/* On mobile, pad iframe so AIUI content ends above the tab bar */
@media (max-width: 767px) {
.chat-iframe-mobile {
padding-bottom: calc(var(--mobile-tab-bar-height, 72px) + 52px);
padding-bottom: var(--mobile-tab-bar-height, 72px);
}
}
@ -380,6 +381,28 @@ input[type="radio"]:active + * {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.3), transparent);
}
/* Tier badges for marketplace */
.tier-badge {
font-size: 0.625rem;
padding: 1px 6px;
border-radius: 9999px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
vertical-align: middle;
margin-left: 6px;
}
.tier-badge-core {
background: rgba(251, 146, 60, 0.2);
color: #fb923c;
}
.tier-badge-recommended {
background: rgba(59, 130, 246, 0.2);
color: #60a5fa;
}
.glass-button-sm {
padding-block: 0.375rem;
padding-inline: 0.75rem;

View File

@ -150,7 +150,14 @@
</svg>
</div>
<div class="flex-1">
<h3 class="text-xl font-semibold text-white mb-1">{{ app.title }}</h3>
<h3 class="text-xl font-semibold text-white mb-1">
{{ app.title }}
<span
v-if="getAppTier(app.id) !== 'optional'"
class="tier-badge"
:class="getAppTier(app.id) === 'core' ? 'tier-badge-core' : 'tier-badge-recommended'"
>{{ getAppTier(app.id) }}</span>
</h3>
<p class="text-sm text-white/60">{{ app.version ? `v${app.version}` : 'latest' }}</p>
<p v-if="app.author" class="text-xs text-white/50 mt-1">by {{ app.author }}</p>
</div>
@ -730,6 +737,15 @@ async function loadCommunityMarketplace() {
loadingCommunity.value = false
}
// Get app tier classification (matches backend get_app_tier)
function getAppTier(appId: string): string {
const core = ['bitcoin-knots', 'bitcoin', 'lnd', 'mempool', 'electrs', 'btcpay-server', 'dwn', 'filebrowser']
const recommended = ['fedimint', 'vaultwarden', 'uptime-kuma', 'grafana', 'searxng', 'tailscale', 'portainer']
if (core.includes(appId)) return 'core'
if (recommended.includes(appId)) return 'recommended'
return 'optional'
}
// Curated list of apps with Docker Hub images
function getCuratedAppList() {
return [