feat(account): show next payment date with lazy Helcim refresh
Persist nextBillingDate on subscription create/update; unset on cancel or downgrade to free. Account page displays the cached date and lazily refreshes from Helcim when the cached value is within 24h of now (or missing).
This commit is contained in:
parent
4da0265935
commit
5d6fcdd78d
8 changed files with 146 additions and 3 deletions
|
|
@ -59,6 +59,10 @@
|
|||
<span class="membership-k">Contribution</span>
|
||||
<span class="membership-v">{{ currentContributionLabel }}</span>
|
||||
</div>
|
||||
<div v-if="nextPaymentDate" class="membership-row">
|
||||
<span class="membership-k">Next payment</span>
|
||||
<span class="membership-v">{{ formatNextPaymentDate(nextPaymentDate) }}</span>
|
||||
</div>
|
||||
<div class="membership-row">
|
||||
<span class="membership-k">Member since</span>
|
||||
<span class="membership-v">{{
|
||||
|
|
@ -305,6 +309,18 @@ const paymentHistoryLoading = ref(false);
|
|||
const paymentHistoryError = ref(false);
|
||||
const paymentHistoryLoaded = ref(false);
|
||||
|
||||
// Next payment (refreshed lazily from Helcim when cached date is stale)
|
||||
const refreshedNextBillingDate = ref(null);
|
||||
const nextBillingRefreshed = ref(false);
|
||||
|
||||
const nextPaymentDate = computed(() => {
|
||||
const m = memberData.value;
|
||||
if (!m) return null;
|
||||
if (m.status !== 'active') return null;
|
||||
if (String(m.contributionTier || '0') === '0') return null;
|
||||
return refreshedNextBillingDate.value || m.nextBillingDate || null;
|
||||
});
|
||||
|
||||
// Change-card state
|
||||
const isChangingCard = ref(false);
|
||||
const changeCardButtonLabel = ref("Change card");
|
||||
|
|
@ -393,6 +409,44 @@ const formatMemberSince = (dateStr) => {
|
|||
});
|
||||
};
|
||||
|
||||
const formatNextPaymentDate = (dateStr) => {
|
||||
if (!dateStr) return "";
|
||||
const d = new Date(dateStr);
|
||||
if (Number.isNaN(d.getTime())) return "";
|
||||
return d.toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const STALE_WINDOW_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
const isNextBillingStale = (dateStr) => {
|
||||
if (!dateStr) return true;
|
||||
const d = new Date(dateStr);
|
||||
if (Number.isNaN(d.getTime())) return true;
|
||||
return d.getTime() - Date.now() < STALE_WINDOW_MS;
|
||||
};
|
||||
|
||||
const refreshNextBillingIfStale = async () => {
|
||||
if (nextBillingRefreshed.value) return;
|
||||
const m = memberData.value;
|
||||
if (!m) return;
|
||||
if (m.status !== 'active') return;
|
||||
if (String(m.contributionTier || '0') === '0') return;
|
||||
if (!isNextBillingStale(m.nextBillingDate)) return;
|
||||
|
||||
nextBillingRefreshed.value = true;
|
||||
try {
|
||||
const response = await $fetch("/api/helcim/subscription");
|
||||
const fresh = response?.subscription?.nextBillingDate;
|
||||
if (fresh) refreshedNextBillingDate.value = fresh;
|
||||
} catch (err) {
|
||||
// Silent — fall back to cached value (if any)
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateTier = async () => {
|
||||
isUpdating.value = true;
|
||||
try {
|
||||
|
|
@ -504,6 +558,7 @@ onMounted(() => {
|
|||
if (memberData.value?.helcimCustomerId) {
|
||||
loadPaymentHistory();
|
||||
}
|
||||
refreshNextBillingIfStale();
|
||||
});
|
||||
|
||||
watch(
|
||||
|
|
@ -515,6 +570,13 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => memberData.value?.status,
|
||||
() => {
|
||||
refreshNextBillingIfStale();
|
||||
},
|
||||
);
|
||||
|
||||
const formatTxnDate = (iso) => {
|
||||
if (!iso) return "—";
|
||||
const d = new Date(iso);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue