feat(join): replace tier dropdown with amount input + guidance chips
This commit is contained in:
parent
50a1ffe735
commit
4d10c4e0a2
1 changed files with 91 additions and 56 deletions
|
|
@ -32,7 +32,7 @@
|
||||||
<DashedBox :hoverable="false">
|
<DashedBox :hoverable="false">
|
||||||
<div class="section-label">Contribution</div>
|
<div class="section-label">Contribution</div>
|
||||||
<div class="info-value">
|
<div class="info-value">
|
||||||
${{ memberData?.contributionTier || "0" }} CAD/month
|
${{ memberData?.contributionAmount ?? 0 }} CAD/month
|
||||||
</div>
|
</div>
|
||||||
</DashedBox>
|
</DashedBox>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -69,14 +69,14 @@
|
||||||
<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">{{ formatTierAmount('5') }}</span> I can contribute</li>
|
<li><span class="tier-amt">{{ formatContributionAmount(5) }}</span> I can contribute</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="tier-amt">{{ formatTierAmount('15') }}</span> I can sustain the community
|
<span class="tier-amt">{{ formatContributionAmount(15) }}</span> I can sustain the community
|
||||||
(suggested)
|
(suggested)
|
||||||
</li>
|
</li>
|
||||||
<li><span class="tier-amt">{{ formatTierAmount('30') }}</span> I can support others too</li>
|
<li><span class="tier-amt">{{ formatContributionAmount(30) }}</span> I can support others too</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="tier-amt">{{ formatTierAmount('50') }}</span> I want to sponsor multiple
|
<span class="tier-amt">{{ formatContributionAmount(50) }}</span> I want to sponsor multiple
|
||||||
members
|
members
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -207,27 +207,38 @@
|
||||||
>
|
>
|
||||||
<label for="cadence-annual">
|
<label for="cadence-annual">
|
||||||
<span class="circle-label-name">Annual</span>
|
<span class="circle-label-name">Annual</span>
|
||||||
<span class="circle-label-desc">2 months free</span>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
||||||
>{{ cadence === 'annual' ? 'Annual' : 'Monthly' }} Contribution</label
|
{{ cadence === 'annual' ? 'Annual' : 'Monthly' }} Contribution
|
||||||
>
|
</label>
|
||||||
<USelectMenu
|
<div class="contribution-input-row">
|
||||||
id="join-contribution"
|
<span class="contribution-currency">$</span>
|
||||||
v-model="form.contributionTier"
|
<input
|
||||||
:items="contributionItems"
|
id="join-contribution"
|
||||||
value-key="value"
|
v-model.number="form.contributionAmount"
|
||||||
:search-input="false"
|
type="number"
|
||||||
class="zine-select"
|
min="0"
|
||||||
:ui="{
|
step="1"
|
||||||
content: 'tz-content',
|
inputmode="numeric"
|
||||||
item: 'tz-item',
|
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>
|
||||||
<div class="form-group full-width">
|
<div class="form-group full-width">
|
||||||
<label class="checkbox-label">
|
<label class="checkbox-label">
|
||||||
|
|
@ -371,7 +382,7 @@
|
||||||
<dt>Circle</dt><dd class="capitalize">{{ form.circle }}</dd>
|
<dt>Circle</dt><dd class="capitalize">{{ form.circle }}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="details-row">
|
<div class="details-row">
|
||||||
<dt>Contribution</dt><dd>{{ selectedTier.label }}</dd>
|
<dt>Contribution</dt><dd>{{ formatContributionAmount(form.contributionAmount) }}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</DashedBox>
|
</DashedBox>
|
||||||
|
|
@ -409,9 +420,8 @@ import { reactive, ref, computed, onMounted, onUnmounted } from "vue";
|
||||||
import { getCircleOptions } from "~/config/circles";
|
import { getCircleOptions } from "~/config/circles";
|
||||||
import {
|
import {
|
||||||
requiresPayment,
|
requiresPayment,
|
||||||
getContributionTierByValue,
|
CONTRIBUTION_PRESETS,
|
||||||
getTierAmount,
|
getGuidanceLabel,
|
||||||
CONTRIBUTION_TIERS,
|
|
||||||
} from "~/config/contributions";
|
} from "~/config/contributions";
|
||||||
|
|
||||||
// Auth state
|
// Auth state
|
||||||
|
|
@ -427,7 +437,7 @@ const form = reactive({
|
||||||
email: "",
|
email: "",
|
||||||
name: "",
|
name: "",
|
||||||
circle: "community",
|
circle: "community",
|
||||||
contributionTier: "15",
|
contributionAmount: 15,
|
||||||
agreedToGuidelines: false,
|
agreedToGuidelines: false,
|
||||||
billingAddress: {
|
billingAddress: {
|
||||||
street: "",
|
street: "",
|
||||||
|
|
@ -461,29 +471,11 @@ const paymentToken = ref(null);
|
||||||
// Circle options from central config
|
// Circle options from central config
|
||||||
const circleOptions = getCircleOptions();
|
const circleOptions = getCircleOptions();
|
||||||
|
|
||||||
// Minimal labels for the dropdown — reactive to cadence.
|
const formatContributionAmount = (amount) => {
|
||||||
// In annual mode, show both monthly and annual price so $50/yr (the $5 tier annual)
|
if (!amount || amount === 0) return "$0";
|
||||||
// is visually distinct from $500/yr (the $50 tier annual).
|
const display = cadence.value === "annual" ? amount * 12 : amount;
|
||||||
const contributionItems = computed(() => {
|
|
||||||
return Object.values(CONTRIBUTION_TIERS).map((tier) => {
|
|
||||||
const base = tier.amount;
|
|
||||||
if (base === 0) return { value: tier.value, label: "$0" };
|
|
||||||
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}` };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const formatTierAmount = (value) => {
|
|
||||||
const tier = getContributionTierByValue(value);
|
|
||||||
if (!tier || tier.amount === 0) return "$0";
|
|
||||||
const amt = getTierAmount(tier, cadence.value);
|
|
||||||
const suffix = cadence.value === "annual" ? "/yr" : "/mo";
|
const suffix = cadence.value === "annual" ? "/yr" : "/mo";
|
||||||
return `$${amt}${suffix}`;
|
return `$${display}${suffix}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize composables
|
// Initialize composables
|
||||||
|
|
@ -499,20 +491,17 @@ const isFormValid = computed(() => {
|
||||||
form.name &&
|
form.name &&
|
||||||
form.email &&
|
form.email &&
|
||||||
form.circle &&
|
form.circle &&
|
||||||
form.contributionTier &&
|
Number.isInteger(form.contributionAmount) && form.contributionAmount >= 0 &&
|
||||||
form.agreedToGuidelines
|
form.agreedToGuidelines
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if payment is required
|
// Check if payment is required
|
||||||
const needsPayment = computed(() => {
|
const needsPayment = computed(() => {
|
||||||
return requiresPayment(form.contributionTier);
|
return requiresPayment(form.contributionAmount);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get selected tier info
|
const guidanceLabel = computed(() => getGuidanceLabel(form.contributionAmount));
|
||||||
const selectedTier = computed(() => {
|
|
||||||
return getContributionTierByValue(form.contributionTier);
|
|
||||||
});
|
|
||||||
|
|
||||||
const flowStepLabel = computed(() => {
|
const flowStepLabel = computed(() => {
|
||||||
switch (flowState.value) {
|
switch (flowState.value) {
|
||||||
|
|
@ -546,7 +535,7 @@ const handleSubmit = async () => {
|
||||||
name: form.name,
|
name: form.name,
|
||||||
email: form.email,
|
email: form.email,
|
||||||
circle: form.circle,
|
circle: form.circle,
|
||||||
contributionTier: form.contributionTier,
|
contributionAmount: form.contributionAmount,
|
||||||
agreedToGuidelines: form.agreedToGuidelines,
|
agreedToGuidelines: form.agreedToGuidelines,
|
||||||
billingAddress: form.billingAddress,
|
billingAddress: form.billingAddress,
|
||||||
},
|
},
|
||||||
|
|
@ -617,7 +606,7 @@ const createSubscription = async (cardToken = null) => {
|
||||||
body: {
|
body: {
|
||||||
customerId: customerId.value,
|
customerId: customerId.value,
|
||||||
customerCode: customerCode.value,
|
customerCode: customerCode.value,
|
||||||
contributionTier: form.contributionTier,
|
contributionAmount: form.contributionAmount,
|
||||||
cadence: cadence.value,
|
cadence: cadence.value,
|
||||||
cardToken: cardToken,
|
cardToken: cardToken,
|
||||||
},
|
},
|
||||||
|
|
@ -883,6 +872,52 @@ onUnmounted(() => {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- 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);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- CIRCLE RADIOS ---- */
|
/* ---- CIRCLE RADIOS ---- */
|
||||||
.circle-radios {
|
.circle-radios {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue