feat(contribution): port account.vue to ContributionAmountField
Replace the inline contribution UI (label, input row, presets, guidance) with the shared ContributionAmountField component, locking cadence and suppressing its built-in summary (account.vue has its own change hint). Fix three computeds that double-applied the cadence conversion now that Member.contributionAmount is stored in cadence-unit (post-Task 7): contributionChangeHint, currentContributionLabel, and nextChargeAmount no longer multiply annual amounts by 12. Convert form.contributionAmount to a monthly-equivalent before the payment-setup redirect — that page is monthly-only and would otherwise attempt an annual-sized monthly charge for annual members. Drop the now-unused guidanceLabel computed, the CONTRIBUTION_PRESETS and getGuidanceLabel imports, and the dead contribution-* CSS rules.
This commit is contained in:
parent
aa6a176fb9
commit
ad63a37a05
1 changed files with 18 additions and 83 deletions
|
|
@ -245,35 +245,13 @@
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<div class="section-label">Change Contribution</div>
|
<div class="section-label">Change Contribution</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<ContributionAmountField
|
||||||
<label class="form-label" for="account-contribution">
|
v-model="form.contributionAmount"
|
||||||
Monthly Contribution
|
:cadence="cadence"
|
||||||
</label>
|
:allow-cadence-change="false"
|
||||||
<div class="contribution-input-row">
|
:show-summary="false"
|
||||||
<span class="contribution-currency">$</span>
|
/>
|
||||||
<input
|
|
||||||
id="account-contribution"
|
|
||||||
v-model.number="form.contributionAmount"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
inputmode="numeric"
|
|
||||||
class="contribution-input"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="contribution-presets" role="group" aria-label="Suggested amounts">
|
|
||||||
<button
|
|
||||||
v-for="preset in CONTRIBUTION_PRESETS"
|
|
||||||
:key="preset.amount"
|
|
||||||
type="button"
|
|
||||||
class="contribution-preset-chip"
|
|
||||||
@click="form.contributionAmount = preset.amount"
|
|
||||||
>
|
|
||||||
${{ preset.amount }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p v-if="guidanceLabel" class="contribution-guidance">{{ guidanceLabel }}</p>
|
|
||||||
</div>
|
|
||||||
<div v-if="contributionChangeHint" class="tier-hint">
|
<div v-if="contributionChangeHint" class="tier-hint">
|
||||||
{{ contributionChangeHint }}
|
{{ contributionChangeHint }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -314,7 +292,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { CONTRIBUTION_PRESETS, getGuidanceLabel, requiresPayment } from '~/config/contributions';
|
import { requiresPayment } from '~/config/contributions';
|
||||||
import { STATUS_LABELS } from '~/config/memberStatus';
|
import { STATUS_LABELS } from '~/config/memberStatus';
|
||||||
|
|
||||||
useSiteMeta({ title: 'Account', noindex: true });
|
useSiteMeta({ title: 'Account', noindex: true });
|
||||||
|
|
@ -373,14 +351,12 @@ const canChangeCard = computed(() => {
|
||||||
|
|
||||||
const cadence = computed(() => memberData.value?.billingCadence || 'monthly');
|
const cadence = computed(() => memberData.value?.billingCadence || 'monthly');
|
||||||
|
|
||||||
const guidanceLabel = computed(() => getGuidanceLabel(form.contributionAmount));
|
|
||||||
|
|
||||||
const contributionChangeHint = computed(() => {
|
const contributionChangeHint = computed(() => {
|
||||||
const current = Number(memberData.value?.contributionAmount || 0);
|
const current = Number(memberData.value?.contributionAmount || 0);
|
||||||
const next = Number(form.contributionAmount || 0);
|
const next = Number(form.contributionAmount || 0);
|
||||||
if (current === next) return "";
|
if (current === next) return "";
|
||||||
if (current === 0 && next > 0) {
|
if (current === 0 && next > 0) {
|
||||||
const firstCharge = cadence.value === "annual" ? next * 12 : next;
|
const firstCharge = next;
|
||||||
return `You'll be charged $${firstCharge} today to start your subscription.`;
|
return `You'll be charged $${firstCharge} today to start your subscription.`;
|
||||||
}
|
}
|
||||||
if (current > 0 && next === 0) {
|
if (current > 0 && next === 0) {
|
||||||
|
|
@ -392,14 +368,13 @@ const contributionChangeHint = computed(() => {
|
||||||
const currentContributionLabel = computed(() => {
|
const currentContributionLabel = computed(() => {
|
||||||
const amount = Number(memberData.value?.contributionAmount || 0);
|
const amount = Number(memberData.value?.contributionAmount || 0);
|
||||||
if (!amount) return '$0';
|
if (!amount) return '$0';
|
||||||
const displayAmount = cadence.value === 'annual' ? amount * 12 : amount;
|
return cadence.value === 'annual' ? `$${amount} / year` : `$${amount} / month`;
|
||||||
return cadence.value === 'annual' ? `$${displayAmount} / year` : `$${displayAmount} / month`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const nextChargeAmount = computed(() => {
|
const nextChargeAmount = computed(() => {
|
||||||
const amount = Number(memberData.value?.contributionAmount || 0);
|
const amount = Number(memberData.value?.contributionAmount || 0);
|
||||||
if (!amount) return null;
|
if (!amount) return null;
|
||||||
return cadence.value === 'annual' ? amount * 12 : amount;
|
return amount;
|
||||||
});
|
});
|
||||||
|
|
||||||
const circleOptions = [
|
const circleOptions = [
|
||||||
|
|
@ -493,8 +468,14 @@ const handleUpdateContribution = async () => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Paid upgrade without a saved card — route to payment setup instead of erroring.
|
// Paid upgrade without a saved card — route to payment setup instead of erroring.
|
||||||
if (err.data?.data?.requiresPaymentSetup) {
|
if (err.data?.data?.requiresPaymentSetup) {
|
||||||
|
// payment-setup.vue is monthly-only (always sends cadence: 'monthly') and
|
||||||
|
// form.contributionAmount is in cadence-unit. For annual members, convert
|
||||||
|
// to monthly-equivalent so we don't trigger an annual-sized monthly charge.
|
||||||
|
const monthlyTier = cadence.value === "annual"
|
||||||
|
? Math.floor(form.contributionAmount / 12)
|
||||||
|
: form.contributionAmount;
|
||||||
await navigateTo(
|
await navigateTo(
|
||||||
`/member/payment-setup?tier=${form.contributionAmount}&circle=${
|
`/member/payment-setup?tier=${monthlyTier}&circle=${
|
||||||
selectedCircle.value || memberData.value?.circle || 'community'
|
selectedCircle.value || memberData.value?.circle || 'community'
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
|
|
@ -890,52 +871,6 @@ const confirmCancelMembership = async () => {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- CONTRIBUTION AMOUNT INPUT + CHIPS ---- */
|
|
||||||
.contribution-input-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
.contribution-currency {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.contribution-input {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
background: var(--input-bg);
|
|
||||||
border: 1px solid var(--parch);
|
|
||||||
font-family: 'Commit Mono', monospace;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
.contribution-input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--candle);
|
|
||||||
}
|
|
||||||
.contribution-presets {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
.contribution-preset-chip {
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px dashed var(--parch);
|
|
||||||
font-family: 'Commit Mono', monospace;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.contribution-preset-chip:hover {
|
|
||||||
border-style: solid;
|
|
||||||
border-color: var(--candle);
|
|
||||||
}
|
|
||||||
.contribution-guidance {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-style: italic;
|
|
||||||
color: var(--ink-soft, currentColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-section {
|
.btn-section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue