feat(join): cadence selector with annual pricing (monthly×10)
Radio-pair cadence selector (Monthly / Annual) added to the join form, reusing the existing .circle-radio styling. contributionItems computed reactively; all tier labels and the left-column price list update on toggle. cadence submitted with the subscription payload. payment-setup hardcoded to monthly (annual upgrades go through /join).
This commit is contained in:
parent
0eeed94772
commit
cd0d3f7167
2 changed files with 61 additions and 15 deletions
|
|
@ -116,19 +116,19 @@
|
||||||
<!-- Left: Monthly Contribution -->
|
<!-- Left: Monthly Contribution -->
|
||||||
<div class="join-col">
|
<div class="join-col">
|
||||||
<div class="section-label" style="margin-bottom: 12px">
|
<div class="section-label" style="margin-bottom: 12px">
|
||||||
Monthly Contribution
|
{{ cadence === 'annual' ? 'Annual Contribution' : 'Monthly Contribution' }}
|
||||||
</div>
|
</div>
|
||||||
<h2>Pay what you can</h2>
|
<h2>Pay what you can</h2>
|
||||||
<ul class="tier-list">
|
<ul class="tier-list">
|
||||||
<li><span class="tier-amt">$0</span> I need support right now</li>
|
<li><span class="tier-amt">$0</span> I need support right now</li>
|
||||||
<li><span class="tier-amt">$5</span> I can contribute</li>
|
<li><span class="tier-amt">{{ cadence === 'annual' ? '$50/yr' : '$5/mo' }}</span> I can contribute</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="tier-amt">$15</span> I can sustain the community
|
<span class="tier-amt">{{ cadence === 'annual' ? '$150/yr' : '$15/mo' }}</span> I can sustain the community
|
||||||
(suggested)
|
(suggested)
|
||||||
</li>
|
</li>
|
||||||
<li><span class="tier-amt">$30</span> I can support others too</li>
|
<li><span class="tier-amt">{{ cadence === 'annual' ? '$300/yr' : '$30/mo' }}</span> I can support others too</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="tier-amt">$50</span> I want to sponsor multiple
|
<span class="tier-amt">{{ cadence === 'annual' ? '$500/yr' : '$50/mo' }}</span> I want to sponsor multiple
|
||||||
members
|
members
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -234,9 +234,39 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Billing Cadence</label>
|
||||||
|
<div class="cadence-radios">
|
||||||
|
<div class="circle-radio">
|
||||||
|
<input
|
||||||
|
id="cadence-monthly"
|
||||||
|
v-model="cadence"
|
||||||
|
type="radio"
|
||||||
|
name="cadence"
|
||||||
|
value="monthly"
|
||||||
|
>
|
||||||
|
<label for="cadence-monthly">
|
||||||
|
<span class="circle-label-name">Monthly</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="circle-radio">
|
||||||
|
<input
|
||||||
|
id="cadence-annual"
|
||||||
|
v-model="cadence"
|
||||||
|
type="radio"
|
||||||
|
name="cadence"
|
||||||
|
value="annual"
|
||||||
|
>
|
||||||
|
<label for="cadence-annual">
|
||||||
|
<span class="circle-label-name">Annual</span>
|
||||||
|
<span class="circle-label-desc">2 months free</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label" for="join-contribution"
|
<label class="form-label" for="join-contribution"
|
||||||
>Monthly Contribution</label
|
>{{ cadence === 'annual' ? 'Annual' : 'Monthly' }} Contribution</label
|
||||||
>
|
>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
id="join-contribution"
|
id="join-contribution"
|
||||||
|
|
@ -393,6 +423,8 @@ import {
|
||||||
getContributionOptions,
|
getContributionOptions,
|
||||||
requiresPayment,
|
requiresPayment,
|
||||||
getContributionTierByValue,
|
getContributionTierByValue,
|
||||||
|
getTierAmount,
|
||||||
|
CONTRIBUTION_TIERS,
|
||||||
} from "~/config/contributions";
|
} from "~/config/contributions";
|
||||||
|
|
||||||
// Auth state
|
// Auth state
|
||||||
|
|
@ -424,6 +456,7 @@ const isSubmitting = ref(false);
|
||||||
const currentStep = ref(1); // 1: Info, 2: Billing (paid only), 3: Payment, 4: Confirmation
|
const currentStep = ref(1); // 1: Info, 2: Billing (paid only), 3: Payment, 4: Confirmation
|
||||||
const errorMessage = ref("");
|
const errorMessage = ref("");
|
||||||
const successMessage = ref("");
|
const successMessage = ref("");
|
||||||
|
const cadence = ref("monthly"); // 'monthly' | 'annual'
|
||||||
|
|
||||||
// Helcim state
|
// Helcim state
|
||||||
const customerId = ref(null);
|
const customerId = ref(null);
|
||||||
|
|
@ -437,14 +470,18 @@ const circleOptions = getCircleOptions();
|
||||||
// Contribution options from central config
|
// Contribution options from central config
|
||||||
const contributionOptions = getContributionOptions();
|
const contributionOptions = getContributionOptions();
|
||||||
|
|
||||||
// Minimal labels for the dropdown (tier descriptions live in the left column).
|
// Minimal labels for the dropdown — reactive to cadence.
|
||||||
const contributionItems = [
|
const contributionItems = computed(() => {
|
||||||
{ value: "0", label: "$0/mo" },
|
const isAnnual = cadence.value === "annual";
|
||||||
{ value: "5", label: "$5/mo" },
|
return Object.values(CONTRIBUTION_TIERS).map((tier) => {
|
||||||
{ value: "15", label: "$15/mo (suggested)" },
|
const base = tier.amount;
|
||||||
{ value: "30", label: "$30/mo" },
|
if (base === 0) return { value: tier.value, label: "$0" };
|
||||||
{ value: "50", label: "$50/mo" },
|
const amt = isAnnual ? base * 10 : base;
|
||||||
];
|
const suffix = isAnnual ? "/yr" : "/mo";
|
||||||
|
const hint = tier.value === "15" && !isAnnual ? " (suggested)" : "";
|
||||||
|
return { value: tier.value, label: `$${amt}${suffix}${hint}` };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize composables
|
// Initialize composables
|
||||||
const {
|
const {
|
||||||
|
|
@ -585,6 +622,7 @@ const createSubscription = async (cardToken = null) => {
|
||||||
customerId: customerId.value,
|
customerId: customerId.value,
|
||||||
customerCode: customerCode.value,
|
customerCode: customerCode.value,
|
||||||
contributionTier: form.contributionTier,
|
contributionTier: form.contributionTier,
|
||||||
|
cadence: cadence.value,
|
||||||
cardToken: cardToken,
|
cardToken: cardToken,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -863,6 +901,13 @@ onUnmounted(() => {
|
||||||
color: var(--text-faint);
|
color: var(--text-faint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- CADENCE RADIOS ---- */
|
||||||
|
.cadence-radios {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- CIRCLE RADIOS ---- */
|
/* ---- CIRCLE RADIOS ---- */
|
||||||
.circle-radios {
|
.circle-radios {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,8 @@ const openModal = async () => {
|
||||||
|
|
||||||
await $fetch('/api/members/update-contribution', {
|
await $fetch('/api/members/update-contribution', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { contributionTier: targetTier.value },
|
// cadence: annual upgrades go through /join; this page is monthly-only
|
||||||
|
body: { contributionTier: targetTier.value, cadence: 'monthly' },
|
||||||
});
|
});
|
||||||
|
|
||||||
await checkMemberStatus();
|
await checkMemberStatus();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue