feat(frontend): rename contributionTier → contributionAmount across remaining pages
This commit is contained in:
parent
5ef0cc845f
commit
b17e006d65
6 changed files with 133 additions and 81 deletions
|
|
@ -62,7 +62,7 @@ export const useMemberPayment = () => {
|
||||||
body: {
|
body: {
|
||||||
customerId: customerId.value,
|
customerId: customerId.value,
|
||||||
customerCode: customerCode.value,
|
customerCode: customerCode.value,
|
||||||
contributionTier: memberData.value?.contributionTier || '5',
|
contributionAmount: memberData.value?.contributionAmount ?? 5,
|
||||||
cardToken: paymentResult.cardToken,
|
cardToken: paymentResult.cardToken,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -124,18 +124,31 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group full-width">
|
<div class="form-group full-width">
|
||||||
<label class="form-label" for="accept-tier">Monthly Contribution</label>
|
<label class="form-label" for="accept-contribution">Monthly Contribution</label>
|
||||||
<select
|
<div class="contribution-input-row">
|
||||||
id="accept-tier"
|
<span class="contribution-currency">$</span>
|
||||||
v-model="form.contributionTier"
|
<input
|
||||||
class="form-select"
|
id="accept-contribution"
|
||||||
>
|
v-model.number="form.contributionAmount"
|
||||||
<option value="0">$0/mo -- I need support right now</option>
|
type="number"
|
||||||
<option value="5">$5/mo -- I can contribute</option>
|
min="0"
|
||||||
<option value="15">$15/mo -- I can sustain the community (suggested)</option>
|
step="1"
|
||||||
<option value="30">$30/mo -- I can support others too</option>
|
inputmode="numeric"
|
||||||
<option value="50">$50/mo -- I want to sponsor multiple members</option>
|
class="contribution-input"
|
||||||
</select>
|
>
|
||||||
|
</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>
|
||||||
<p class="field-note">Pay what you can. If you can pay more, you're making room for someone who can't.</p>
|
<p class="field-note">Pay what you can. If you can pay more, you're making room for someone who can't.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -171,7 +184,7 @@
|
||||||
<div v-else-if="step === 'payment'" class="form-container">
|
<div v-else-if="step === 'payment'" class="form-container">
|
||||||
<h1>Payment Information</h1>
|
<h1>Payment Information</h1>
|
||||||
<p class="form-intro">
|
<p class="form-intro">
|
||||||
You're signing up for ${{ form.contributionTier }} CAD / month.
|
You're signing up for ${{ form.contributionAmount }} CAD / month.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div v-if="errorMessage" class="error-box">{{ errorMessage }}</div>
|
<div v-if="errorMessage" class="error-box">{{ errorMessage }}</div>
|
||||||
|
|
@ -199,7 +212,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { requiresPayment } from "~/config/contributions";
|
import {
|
||||||
|
requiresPayment,
|
||||||
|
CONTRIBUTION_PRESETS,
|
||||||
|
getGuidanceLabel,
|
||||||
|
} from "~/config/contributions";
|
||||||
|
|
||||||
definePageMeta({ layout: false });
|
definePageMeta({ layout: false });
|
||||||
|
|
||||||
|
|
@ -218,18 +235,26 @@ const form = reactive({
|
||||||
location: "",
|
location: "",
|
||||||
circle: "community",
|
circle: "community",
|
||||||
motivation: "",
|
motivation: "",
|
||||||
contributionTier: "15",
|
contributionAmount: 15,
|
||||||
agreedToGuidelines: false,
|
agreedToGuidelines: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isFormValid = computed(() => {
|
const isFormValid = computed(() => {
|
||||||
return form.name && form.circle && form.contributionTier && form.agreedToGuidelines;
|
return (
|
||||||
|
form.name &&
|
||||||
|
form.circle &&
|
||||||
|
Number.isInteger(form.contributionAmount) &&
|
||||||
|
form.contributionAmount >= 0 &&
|
||||||
|
form.agreedToGuidelines
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const needsPayment = computed(() => {
|
const needsPayment = computed(() => {
|
||||||
return requiresPayment(form.contributionTier);
|
return requiresPayment(form.contributionAmount);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const guidanceLabel = computed(() => getGuidanceLabel(form.contributionAmount));
|
||||||
|
|
||||||
// Helcim state for paid tiers
|
// Helcim state for paid tiers
|
||||||
const memberId = ref(null);
|
const memberId = ref(null);
|
||||||
const customerId = ref(null);
|
const customerId = ref(null);
|
||||||
|
|
@ -280,7 +305,7 @@ const handleAccept = async () => {
|
||||||
location: form.location || undefined,
|
location: form.location || undefined,
|
||||||
circle: form.circle,
|
circle: form.circle,
|
||||||
motivation: form.motivation || undefined,
|
motivation: form.motivation || undefined,
|
||||||
contributionTier: form.contributionTier,
|
contributionAmount: form.contributionAmount,
|
||||||
agreedToGuidelines: form.agreedToGuidelines,
|
agreedToGuidelines: form.agreedToGuidelines,
|
||||||
token: token.value,
|
token: token.value,
|
||||||
},
|
},
|
||||||
|
|
@ -314,7 +339,7 @@ const setupPayment = async (member) => {
|
||||||
name: member.name,
|
name: member.name,
|
||||||
email: member.email,
|
email: member.email,
|
||||||
circle: member.circle,
|
circle: member.circle,
|
||||||
contributionTier: form.contributionTier,
|
contributionAmount: form.contributionAmount,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -358,7 +383,7 @@ const processPayment = async () => {
|
||||||
body: {
|
body: {
|
||||||
customerId: customerId.value,
|
customerId: customerId.value,
|
||||||
customerCode: customerCode.value,
|
customerCode: customerCode.value,
|
||||||
contributionTier: form.contributionTier,
|
contributionAmount: form.contributionAmount,
|
||||||
cardToken: paymentResult.cardToken,
|
cardToken: paymentResult.cardToken,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -487,6 +512,52 @@ textarea.form-input {
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- 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;
|
||||||
|
|
|
||||||
|
|
@ -54,14 +54,8 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Contribution tier ($/mo)</label>
|
<label>Contribution ($/mo)</label>
|
||||||
<select v-model="form.contributionTier">
|
<input v-model.number="form.contributionAmount" type="number" min="0" step="1">
|
||||||
<option value="0">$0</option>
|
|
||||||
<option value="5">$5</option>
|
|
||||||
<option value="15">$15</option>
|
|
||||||
<option value="30">$30</option>
|
|
||||||
<option value="50">$50</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Status</label>
|
<label>Status</label>
|
||||||
|
|
@ -270,7 +264,7 @@ const form = reactive({
|
||||||
name: "",
|
name: "",
|
||||||
email: "",
|
email: "",
|
||||||
circle: "",
|
circle: "",
|
||||||
contributionTier: "",
|
contributionAmount: 0,
|
||||||
status: "",
|
status: "",
|
||||||
role: "",
|
role: "",
|
||||||
});
|
});
|
||||||
|
|
@ -282,7 +276,7 @@ function populateForm(m) {
|
||||||
form.name = m.name;
|
form.name = m.name;
|
||||||
form.email = m.email;
|
form.email = m.email;
|
||||||
form.circle = m.circle;
|
form.circle = m.circle;
|
||||||
form.contributionTier = String(m.contributionTier);
|
form.contributionAmount = m.contributionAmount ?? 0;
|
||||||
form.status = m.status || "pending_payment";
|
form.status = m.status || "pending_payment";
|
||||||
form.role = m.role || "member";
|
form.role = m.role || "member";
|
||||||
}
|
}
|
||||||
|
|
@ -304,7 +298,7 @@ async function submitEdit() {
|
||||||
name: form.name,
|
name: form.name,
|
||||||
email: form.email,
|
email: form.email,
|
||||||
circle: form.circle,
|
circle: form.circle,
|
||||||
contributionTier: form.contributionTier,
|
contributionAmount: form.contributionAmount,
|
||||||
status: form.status,
|
status: form.status,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
<th class="sortable" @click="toggleSort('name')">Name <span class="sort-ind">{{ sortIndicator('name') }}</span></th>
|
<th class="sortable" @click="toggleSort('name')">Name <span class="sort-ind">{{ sortIndicator('name') }}</span></th>
|
||||||
<th class="sortable" @click="toggleSort('email')">Email <span class="sort-ind">{{ sortIndicator('email') }}</span></th>
|
<th class="sortable" @click="toggleSort('email')">Email <span class="sort-ind">{{ sortIndicator('email') }}</span></th>
|
||||||
<th class="sortable" @click="toggleSort('circle')">Circle <span class="sort-ind">{{ sortIndicator('circle') }}</span></th>
|
<th class="sortable" @click="toggleSort('circle')">Circle <span class="sort-ind">{{ sortIndicator('circle') }}</span></th>
|
||||||
<th class="sortable" @click="toggleSort('contributionTier')">Tier <span class="sort-ind">{{ sortIndicator('contributionTier') }}</span></th>
|
<th class="sortable" @click="toggleSort('contributionAmount')">Contribution <span class="sort-ind">{{ sortIndicator('contributionAmount') }}</span></th>
|
||||||
<th class="sortable" @click="toggleSort('status')">Status <span class="sort-ind">{{ sortIndicator('status') }}</span></th>
|
<th class="sortable" @click="toggleSort('status')">Status <span class="sort-ind">{{ sortIndicator('status') }}</span></th>
|
||||||
<th>Invite</th>
|
<th>Invite</th>
|
||||||
<th>Slack</th>
|
<th>Slack</th>
|
||||||
|
|
@ -112,7 +112,7 @@
|
||||||
member.circle
|
member.circle
|
||||||
}}</span>
|
}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="col-mono">${{ member.contributionTier }}/mo</td>
|
<td class="col-mono">${{ member.contributionAmount ?? 0 }}/mo</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge status" :class="`status-${member.status || 'pending_payment'}`">{{ statusLabel(member.status) }}</span>
|
<span class="badge status" :class="`status-${member.status || 'pending_payment'}`">{{ statusLabel(member.status) }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -186,14 +186,8 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Contribution Tier</label>
|
<label>Contribution ($/mo)</label>
|
||||||
<select v-model="newMember.contributionTier">
|
<input v-model.number="newMember.contributionAmount" type="number" min="0" step="1">
|
||||||
<option value="0">$0/month</option>
|
|
||||||
<option value="5">$5/month</option>
|
|
||||||
<option value="15">$15/month</option>
|
|
||||||
<option value="30">$30/month</option>
|
|
||||||
<option value="50">$50/month</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button type="button" class="btn" @click="showCreateModal = false">
|
<button type="button" class="btn" @click="showCreateModal = false">
|
||||||
|
|
@ -223,11 +217,10 @@
|
||||||
<div v-if="!csvRows.length">
|
<div v-if="!csvRows.length">
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
Upload a CSV with columns:
|
Upload a CSV with columns:
|
||||||
<code>name,email,circle,contributionTier</code>
|
<code>name,email,circle,contributionAmount</code>
|
||||||
</p>
|
</p>
|
||||||
<p class="help-text" style="margin-bottom: 12px">
|
<p class="help-text" style="margin-bottom: 12px">
|
||||||
Valid circles: community, founder, practitioner. Valid tiers: 0,
|
Valid circles: community, founder, practitioner. Contribution: whole number ≥ 0.
|
||||||
5, 15, 30, 50.
|
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
ref="csvFileInput"
|
ref="csvFileInput"
|
||||||
|
|
@ -287,7 +280,7 @@
|
||||||
<td>{{ row.name }}</td>
|
<td>{{ row.name }}</td>
|
||||||
<td class="col-email">{{ row.email }}</td>
|
<td class="col-email">{{ row.email }}</td>
|
||||||
<td>{{ row.circle }}</td>
|
<td>{{ row.circle }}</td>
|
||||||
<td>${{ row.contributionTier }}/mo</td>
|
<td>${{ row.contributionAmount }}/mo</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -367,14 +360,8 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Contribution Tier</label>
|
<label>Contribution ($/mo)</label>
|
||||||
<select v-model="editingMember.contributionTier">
|
<input v-model.number="editingMember.contributionAmount" type="number" min="0" step="1">
|
||||||
<option value="0">$0/month</option>
|
|
||||||
<option value="5">$5/month</option>
|
|
||||||
<option value="15">$15/month</option>
|
|
||||||
<option value="30">$30/month</option>
|
|
||||||
<option value="50">$50/month</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Status</label>
|
<label>Status</label>
|
||||||
|
|
@ -550,7 +537,7 @@ const newMember = reactive({
|
||||||
name: "",
|
name: "",
|
||||||
email: "",
|
email: "",
|
||||||
circle: "community",
|
circle: "community",
|
||||||
contributionTier: "0",
|
contributionAmount: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredMembers = computed(() => {
|
const filteredMembers = computed(() => {
|
||||||
|
|
@ -576,7 +563,7 @@ const filteredMembers = computed(() => {
|
||||||
return [...filtered].sort((a, b) => {
|
return [...filtered].sort((a, b) => {
|
||||||
let av = a[key];
|
let av = a[key];
|
||||||
let bv = b[key];
|
let bv = b[key];
|
||||||
if (key === "contributionTier") {
|
if (key === "contributionAmount") {
|
||||||
av = Number(av) || 0;
|
av = Number(av) || 0;
|
||||||
bv = Number(bv) || 0;
|
bv = Number(bv) || 0;
|
||||||
} else if (key === "createdAt") {
|
} else if (key === "createdAt") {
|
||||||
|
|
@ -668,7 +655,7 @@ const createMember = async () => {
|
||||||
name: "",
|
name: "",
|
||||||
email: "",
|
email: "",
|
||||||
circle: "community",
|
circle: "community",
|
||||||
contributionTier: "0",
|
contributionAmount: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
await refresh();
|
await refresh();
|
||||||
|
|
@ -687,7 +674,6 @@ const createMember = async () => {
|
||||||
|
|
||||||
// --- CSV Import ---
|
// --- CSV Import ---
|
||||||
const VALID_CIRCLES = ["community", "founder", "practitioner"];
|
const VALID_CIRCLES = ["community", "founder", "practitioner"];
|
||||||
const VALID_TIERS = ["0", "5", "15", "30", "50"];
|
|
||||||
|
|
||||||
const handleCsvFile = (event) => {
|
const handleCsvFile = (event) => {
|
||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
|
|
@ -716,10 +702,10 @@ const parseCsv = (text) => {
|
||||||
const nameIdx = header.indexOf("name");
|
const nameIdx = header.indexOf("name");
|
||||||
const emailIdx = header.indexOf("email");
|
const emailIdx = header.indexOf("email");
|
||||||
const circleIdx = header.indexOf("circle");
|
const circleIdx = header.indexOf("circle");
|
||||||
const tierIdx = header.indexOf("contributiontier");
|
const amountIdx = header.indexOf("contributionamount");
|
||||||
|
|
||||||
if (nameIdx === -1 || emailIdx === -1 || circleIdx === -1 || tierIdx === -1) {
|
if (nameIdx === -1 || emailIdx === -1 || circleIdx === -1 || amountIdx === -1) {
|
||||||
csvParseError.value = `Missing required columns. Found: ${header.join(", ")}. Need: name, email, circle, contributionTier`;
|
csvParseError.value = `Missing required columns. Found: ${header.join(", ")}. Need: name, email, circle, contributionAmount`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -731,20 +717,21 @@ const parseCsv = (text) => {
|
||||||
const name = cols[nameIdx] || "";
|
const name = cols[nameIdx] || "";
|
||||||
const email = (cols[emailIdx] || "").toLowerCase();
|
const email = (cols[emailIdx] || "").toLowerCase();
|
||||||
const circle = (cols[circleIdx] || "").toLowerCase();
|
const circle = (cols[circleIdx] || "").toLowerCase();
|
||||||
const contributionTier = cols[tierIdx] || "";
|
const rawAmount = cols[amountIdx] || "";
|
||||||
|
const contributionAmount = Number(rawAmount);
|
||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
if (!name) error = "Missing name";
|
if (!name) error = "Missing name";
|
||||||
else if (!email || !email.includes("@")) error = "Invalid email";
|
else if (!email || !email.includes("@")) error = "Invalid email";
|
||||||
else if (!VALID_CIRCLES.includes(circle))
|
else if (!VALID_CIRCLES.includes(circle))
|
||||||
error = `Invalid circle: ${circle}`;
|
error = `Invalid circle: ${circle}`;
|
||||||
else if (!VALID_TIERS.includes(contributionTier))
|
else if (!Number.isInteger(contributionAmount) || contributionAmount < 0)
|
||||||
error = `Invalid tier: ${contributionTier}`;
|
error = `Invalid contribution: ${rawAmount}`;
|
||||||
else if (seenEmails.has(email)) error = "Duplicate email in CSV";
|
else if (seenEmails.has(email)) error = "Duplicate email in CSV";
|
||||||
|
|
||||||
if (!error) seenEmails.add(email);
|
if (!error) seenEmails.add(email);
|
||||||
|
|
||||||
rows.push({ name, email, circle, contributionTier, error });
|
rows.push({ name, email, circle, contributionAmount, error });
|
||||||
}
|
}
|
||||||
|
|
||||||
csvRows.value = rows;
|
csvRows.value = rows;
|
||||||
|
|
@ -771,11 +758,11 @@ const submitImport = async () => {
|
||||||
importing.value = true;
|
importing.value = true;
|
||||||
try {
|
try {
|
||||||
const payload = csvValidRows.value.map(
|
const payload = csvValidRows.value.map(
|
||||||
({ name, email, circle, contributionTier }) => ({
|
({ name, email, circle, contributionAmount }) => ({
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
circle,
|
circle,
|
||||||
contributionTier,
|
contributionAmount,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -854,7 +841,7 @@ const editingMember = reactive({
|
||||||
name: "",
|
name: "",
|
||||||
email: "",
|
email: "",
|
||||||
circle: "community",
|
circle: "community",
|
||||||
contributionTier: "0",
|
contributionAmount: 0,
|
||||||
status: "pending_payment",
|
status: "pending_payment",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -864,7 +851,7 @@ const editMember = (member) => {
|
||||||
name: member.name,
|
name: member.name,
|
||||||
email: member.email,
|
email: member.email,
|
||||||
circle: member.circle,
|
circle: member.circle,
|
||||||
contributionTier: String(member.contributionTier),
|
contributionAmount: member.contributionAmount ?? 0,
|
||||||
status: member.status || "pending_payment",
|
status: member.status || "pending_payment",
|
||||||
});
|
});
|
||||||
showEditModal.value = true;
|
showEditModal.value = true;
|
||||||
|
|
@ -879,7 +866,7 @@ const submitEditMember = async () => {
|
||||||
name: editingMember.name,
|
name: editingMember.name,
|
||||||
email: editingMember.email,
|
email: editingMember.email,
|
||||||
circle: editingMember.circle,
|
circle: editingMember.circle,
|
||||||
contributionTier: editingMember.contributionTier,
|
contributionAmount: editingMember.contributionAmount,
|
||||||
status: editingMember.status,
|
status: editingMember.status,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
<PageHeader :title="`Welcome back, ${memberData?.name || ''}`">
|
<PageHeader :title="`Welcome back, ${memberData?.name || ''}`">
|
||||||
<div class="dashboard-meta">
|
<div class="dashboard-meta">
|
||||||
<CircleBadge :circle="memberData?.circle || 'community'" />
|
<CircleBadge :circle="memberData?.circle || 'community'" />
|
||||||
<span>${{ memberData?.contributionTier }} CAD/mo</span>
|
<span>${{ memberData?.contributionAmount ?? 0 }} CAD/mo</span>
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
|
|
@ -169,7 +169,7 @@
|
||||||
<div class="membership-row">
|
<div class="membership-row">
|
||||||
<span class="key">Contribution</span>
|
<span class="key">Contribution</span>
|
||||||
<span class="val"
|
<span class="val"
|
||||||
>${{ memberData?.contributionTier }} CAD/month</span
|
>${{ memberData?.contributionAmount ?? 0 }} CAD/month</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="membership-row">
|
<div class="membership-row">
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Set Up Payment"
|
title="Set Up Payment"
|
||||||
:subtitle="targetTier ? `Upgrading to $${targetTier}/month` : 'Payment setup'"
|
:subtitle="targetAmount != null ? `Upgrading to $${targetAmount}/month` : 'Payment setup'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageSection>
|
<PageSection>
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
<div v-else-if="step === 'ready'" class="status-block">
|
<div v-else-if="step === 'ready'" class="status-block">
|
||||||
<p>
|
<p>
|
||||||
To upgrade to <strong>${{ targetTier }}/month</strong>, we need a
|
To upgrade to <strong>${{ targetAmount }}/month</strong>, we need a
|
||||||
payment method on file. Click below to open the secure payment
|
payment method on file. Click below to open the secure payment
|
||||||
form — we'll verify your card with a $0 authorization and then
|
form — we'll verify your card with a $0 authorization and then
|
||||||
activate your new tier.
|
activate your new tier.
|
||||||
|
|
@ -56,12 +56,12 @@ const toast = useToast();
|
||||||
const { memberData, checkMemberStatus } = useAuth();
|
const { memberData, checkMemberStatus } = useAuth();
|
||||||
const { initializeHelcimPay, verifyPayment, cleanup: cleanupHelcim } = useHelcimPay();
|
const { initializeHelcimPay, verifyPayment, cleanup: cleanupHelcim } = useHelcimPay();
|
||||||
|
|
||||||
const VALID_TIERS = ['5', '15', '30', '50'];
|
const VALID_AMOUNTS = [5, 15, 30, 50];
|
||||||
const VALID_CIRCLES = ['community', 'founder', 'practitioner'];
|
const VALID_CIRCLES = ['community', 'founder', 'practitioner'];
|
||||||
|
|
||||||
const targetTier = computed(() => {
|
const targetAmount = computed(() => {
|
||||||
const t = String(route.query.tier || '');
|
const n = Number(route.query.tier);
|
||||||
return VALID_TIERS.includes(t) ? t : null;
|
return VALID_AMOUNTS.includes(n) ? n : null;
|
||||||
});
|
});
|
||||||
const targetCircle = computed(() => {
|
const targetCircle = computed(() => {
|
||||||
const c = String(route.query.circle || '');
|
const c = String(route.query.circle || '');
|
||||||
|
|
@ -78,8 +78,8 @@ const initialize = async () => {
|
||||||
errorMessage.value = '';
|
errorMessage.value = '';
|
||||||
step.value = 'loading';
|
step.value = 'loading';
|
||||||
|
|
||||||
if (!targetTier.value) {
|
if (targetAmount.value == null) {
|
||||||
errorMessage.value = 'Missing or invalid target tier.';
|
errorMessage.value = 'Missing or invalid target amount.';
|
||||||
step.value = 'error';
|
step.value = 'error';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +129,7 @@ const openModal = async () => {
|
||||||
await $fetch('/api/members/update-contribution', {
|
await $fetch('/api/members/update-contribution', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
// cadence: annual upgrades go through /join; this page is monthly-only
|
// cadence: annual upgrades go through /join; this page is monthly-only
|
||||||
body: { contributionTier: targetTier.value, cadence: 'monthly' },
|
body: { contributionAmount: targetAmount.value, cadence: 'monthly' },
|
||||||
});
|
});
|
||||||
|
|
||||||
await checkMemberStatus();
|
await checkMemberStatus();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue