From fd9ce5bc2caa673c0cc81a7ca6bf880c29b1b226 Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Sat, 18 Apr 2026 22:06:38 +0100 Subject: [PATCH] fix(ui): disambiguate annual tier labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "$50/yr" was ambiguous — could mean the $5 tier in annual mode or the $50 tier in monthly mode. On /join the dropdown now shows both prices ("$5/mo → $50/yr") in annual mode. On the account page TierPicker gains a subtitle slot; annual mode shows "$N/mo tier" beneath the annual price so members recognize which tier they're on. --- app/components/TierPicker.vue | 10 ++++++++++ app/pages/join.vue | 13 +++++++++---- app/pages/member/account.vue | 13 +++++++++---- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/app/components/TierPicker.vue b/app/components/TierPicker.vue index bf32c9d..5cf6318 100644 --- a/app/components/TierPicker.vue +++ b/app/components/TierPicker.vue @@ -8,6 +8,7 @@ @click="$emit('update:modelValue', tier.amount)" > {{ tier.display }} + {{ tier.subtitle }} @@ -78,6 +79,15 @@ defineEmits(["update:modelValue"]); color: var(--candle); } +.tier-subtitle { + display: block; + margin-top: 4px; + font-size: 11px; + color: var(--text-dim); + font-family: "Commit Mono", monospace; + letter-spacing: 0.02em; +} + @media (max-width: 768px) { .tier-picker { flex-wrap: wrap; diff --git a/app/pages/join.vue b/app/pages/join.vue index 67d919b..329bbc5 100644 --- a/app/pages/join.vue +++ b/app/pages/join.vue @@ -467,14 +467,19 @@ const paymentToken = ref(null); const circleOptions = getCircleOptions(); // Minimal labels for the dropdown — reactive to cadence. +// In annual mode, show both monthly and annual price so $50/yr (the $5 tier annual) +// is visually distinct from $500/yr (the $50 tier annual). const contributionItems = computed(() => { return Object.values(CONTRIBUTION_TIERS).map((tier) => { const base = tier.amount; if (base === 0) return { value: tier.value, label: "$0" }; - const amt = getTierAmount(tier, cadence.value); - const suffix = cadence.value === "annual" ? "/yr" : "/mo"; - const hint = tier.value === "15" && cadence.value !== "annual" ? " (suggested)" : ""; - return { value: tier.value, label: `$${amt}${suffix}${hint}` }; + const monthlyLabel = `$${base}/mo`; + const priceLabel = + cadence.value === "annual" + ? `${monthlyLabel} → $${getTierAmount(tier, "annual")}/yr` + : monthlyLabel; + const hint = tier.value === "15" ? " (suggested)" : ""; + return { value: tier.value, label: `${priceLabel}${hint}` }; }); }); diff --git a/app/pages/member/account.vue b/app/pages/member/account.vue index 0fd4c50..d16ddab 100644 --- a/app/pages/member/account.vue +++ b/app/pages/member/account.vue @@ -239,10 +239,15 @@ const BASE_TIERS = [ const tiers = computed(() => { const cadence = memberData.value?.billingCadence || 'monthly'; - return BASE_TIERS.map((t) => ({ - ...t, - display: t.amount === 0 ? '$0' : `$${getTierAmount(t, cadence)}`, - })); + return BASE_TIERS.map((t) => { + if (t.amount === 0) return { ...t, display: '$0' }; + const suffix = cadence === 'annual' ? '/yr' : '/mo'; + return { + ...t, + display: `$${getTierAmount(t, cadence)}${suffix}`, + subtitle: cadence === 'annual' ? `$${t.amount}/mo tier` : null, + }; + }); }); const currentContributionLabel = computed(() => {