feat(signup): unify cadence UX across accept-invite, join, and account

Extract shared SignupFlowOverlay component. Static "Monthly Contribution"
label on all three contribution inputs (was misleadingly dynamic).
"Per Year"/"Per Month" toggle copy; Per Year default on accept-invite,
Per Month default on join. Live billing-summary card on both signup
flows. Welcome-heading on dashboard via ?welcome=1 for new signups.
$0-member polish on account page (hide payment-history + Solidarity
Fund prompts). State-aware contribution-change hint. Invite accept now
creates Helcim customer and sets auth cookie server-side for both free
and paid branches. Pre-registrant invite + /join signup flows manually
verified against Cleo Nguyen preReg and $0-$50 variants.
This commit is contained in:
Jennie Robinson Faber 2026-04-20 12:34:59 +01:00
parent 493be2f3bc
commit a80728f0a8
10 changed files with 553 additions and 321 deletions

View file

@ -72,9 +72,9 @@
</div>
</PageSection>
<!-- PAYMENT HISTORY (only when a Helcim customer exists) -->
<!-- PAYMENT HISTORY (only when there's actually a paid plan) -->
<PageSection
v-if="memberData.helcimCustomerId"
v-if="memberData.helcimCustomerId && (memberData.contributionAmount || 0) > 0"
divider="top"
>
<div class="section-label">Payment history</div>
@ -200,11 +200,11 @@
<div class="danger-zone">
<p>
Cancelling closes your account and ends access to member-only
spaces, including Slack. If you're cancelling because of a
spaces, including Slack.<template v-if="(memberData.contributionAmount || 0) > 0"> If you're cancelling because of a
money issue, the
<NuxtLink to="/community-guidelines">Solidarity Fund</NuxtLink>
and the $0 tier are always available reach out before you
go.
go.</template>
</p>
<div v-if="showCancelConfirm" class="cancel-confirm">
<p class="cancel-confirm-prompt">
@ -242,7 +242,7 @@
<div class="form-group">
<label class="form-label" for="account-contribution">
{{ cadence === 'annual' ? 'Annual' : 'Monthly' }} Contribution
Monthly Contribution
</label>
<div class="contribution-input-row">
<span class="contribution-currency">$</span>
@ -269,8 +269,8 @@
</div>
<p v-if="guidanceLabel" class="contribution-guidance">{{ guidanceLabel }}</p>
</div>
<div class="tier-hint">
Changes take effect on your next billing cycle
<div v-if="contributionChangeHint" class="tier-hint">
{{ contributionChangeHint }}
</div>
<button
class="btn btn-primary btn-section"
@ -367,6 +367,20 @@ const cadence = computed(() => memberData.value?.billingCadence || 'monthly');
const guidanceLabel = computed(() => getGuidanceLabel(form.contributionAmount));
const contributionChangeHint = computed(() => {
const current = Number(memberData.value?.contributionAmount || 0);
const next = Number(form.contributionAmount || 0);
if (current === next) return "";
if (current === 0 && next > 0) {
const firstCharge = cadence.value === "annual" ? next * 12 : next;
return `You'll be charged $${firstCharge} today to start your subscription.`;
}
if (current > 0 && next === 0) {
return "Your paid subscription will be cancelled.";
}
return "Changes apply on your next billing cycle.";
});
const currentContributionLabel = computed(() => {
const amount = Number(memberData.value?.contributionAmount || 0);
if (!amount) return '$0';