feat(member): account/profile polish + tier upgrade flow
- Timezone: curated USelectMenu dropdown (app/config/timezones.js), preserves unknown saved values
- Profile save now uses useToast() for success/error; remove inline save banner
- Nav onboarding dot nudged down 1px for optical alignment with lowercase text
- Onboarding: skip a suggestion with POST /api/onboarding/track {skip}; member.onboarding.skipped map; does not affect graduation
- CirclePicker takes :saved-value so 'Current' badge stays until save completes
- PrivacyToggle is binary (USwitch labeled Private); member schema enum reduced to ['members','private']; zod coerces legacy 'public'
- New /member/payment-setup page: HelcimPay $0 verify + update-contribution, wired from account.vue via requiresPaymentSetup redirect
- Helcim portal: NUXT_PUBLIC_HELCIM_PORTAL_URL env + account.vue 'Manage billing in Helcim' link
- Migration script: scripts/migrate-privacy-public-to-members.js
This commit is contained in:
parent
08fc3884da
commit
7292b11c0b
18 changed files with 604 additions and 122 deletions
|
|
@ -68,6 +68,15 @@
|
|||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
v-if="helcimPortalUrl && memberData.helcimCustomerId"
|
||||
:href="helcimPortalUrl"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="billing-link"
|
||||
>
|
||||
Manage billing in Helcim →
|
||||
</a>
|
||||
</PageSection>
|
||||
|
||||
<PageSection divider="top">
|
||||
|
|
@ -178,6 +187,7 @@
|
|||
|
||||
<CirclePicker
|
||||
v-model="selectedCircle"
|
||||
:saved-value="memberData.circle"
|
||||
:circles="circleOptions"
|
||||
/>
|
||||
<button
|
||||
|
|
@ -204,6 +214,7 @@ definePageMeta({
|
|||
const { memberData, checkMemberStatus } = useAuth();
|
||||
const { openLoginModal } = useLoginModal();
|
||||
const toast = useToast();
|
||||
const helcimPortalUrl = useRuntimeConfig().public.helcimPortalUrl || '';
|
||||
|
||||
const selectedTier = ref(0);
|
||||
const selectedCircle = ref("");
|
||||
|
|
@ -276,13 +287,22 @@ const handleUpdateTier = async () => {
|
|||
body: { contributionTier: String(selectedTier.value) },
|
||||
});
|
||||
await checkMemberStatus();
|
||||
toast.add({ title: "Contribution updated", color: "green" });
|
||||
toast.add({ title: "Contribution updated", color: "success" });
|
||||
} catch (err) {
|
||||
// Paid upgrade without a saved card — route to payment setup instead of erroring.
|
||||
if (err.data?.data?.requiresPaymentSetup) {
|
||||
await navigateTo(
|
||||
`/member/payment-setup?tier=${selectedTier.value}&circle=${
|
||||
selectedCircle.value || memberData.value?.circle || 'community'
|
||||
}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
selectedTier.value = Number(memberData.value?.contributionTier || 0);
|
||||
toast.add({
|
||||
title: "Update failed",
|
||||
description: err.data?.statusMessage || "Please try again.",
|
||||
color: "red",
|
||||
color: "error",
|
||||
});
|
||||
} finally {
|
||||
isUpdating.value = false;
|
||||
|
|
@ -297,13 +317,13 @@ const handleUpdateCircle = async () => {
|
|||
body: { circle: selectedCircle.value },
|
||||
});
|
||||
await checkMemberStatus();
|
||||
toast.add({ title: "Circle updated", color: "green" });
|
||||
toast.add({ title: "Circle updated", color: "success" });
|
||||
} catch (err) {
|
||||
selectedCircle.value = memberData.value?.circle || "community";
|
||||
toast.add({
|
||||
title: "Update failed",
|
||||
description: err.data?.statusMessage || "Please try again.",
|
||||
color: "red",
|
||||
color: "error",
|
||||
});
|
||||
} finally {
|
||||
isUpdating.value = false;
|
||||
|
|
@ -326,12 +346,12 @@ const handleUpdateEmail = async () => {
|
|||
});
|
||||
await checkMemberStatus();
|
||||
cancelEmailEdit();
|
||||
toast.add({ title: "Email updated", color: "green" });
|
||||
toast.add({ title: "Email updated", color: "success" });
|
||||
} catch (err) {
|
||||
toast.add({
|
||||
title: "Update failed",
|
||||
description: err.data?.statusMessage || "Please try again.",
|
||||
color: "red",
|
||||
color: "error",
|
||||
});
|
||||
} finally {
|
||||
isUpdatingEmail.value = false;
|
||||
|
|
@ -359,13 +379,13 @@ const confirmCancelMembership = async () => {
|
|||
color: "neutral",
|
||||
});
|
||||
} else {
|
||||
toast.add({ title: "Membership cancelled", color: "orange" });
|
||||
toast.add({ title: "Membership cancelled", color: "warning" });
|
||||
}
|
||||
} catch (err) {
|
||||
toast.add({
|
||||
title: "Cancellation failed",
|
||||
description: err.data?.statusMessage || "Please try again.",
|
||||
color: "red",
|
||||
color: "error",
|
||||
});
|
||||
} finally {
|
||||
isCancelling.value = false;
|
||||
|
|
@ -528,4 +548,16 @@ const confirmCancelMembership = async () => {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.billing-link {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--candle);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.billing-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue