diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 4b39e60..4167651 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -273,6 +273,14 @@ p a, blockquote a { min-width: 0; } +/* ---- Nuxt UI placeholder contrast ---- + Default Nuxt UI placeholder uses `text-dimmed` (#a6a09b) which fails WCAG + AA on cream and white backgrounds (≈2.4:1). Override globally to --text-dim + so USelect/USelectMenu placeholders meet the 4.5:1 ratio. */ +[data-slot="placeholder"] { + color: var(--text-dim); +} + /* ---- SHARED USelectMenu STYLES ---- Apply via: Classes are global (not scoped) because Nuxt UI portals the popup content to body. */ diff --git a/app/composables/useMemberPayment.js b/app/composables/useMemberPayment.js index 23255a7..fcab6fe 100644 --- a/app/composables/useMemberPayment.js +++ b/app/composables/useMemberPayment.js @@ -25,17 +25,45 @@ export const useMemberPayment = () => { paymentSuccess.value = false try { - // Skip HelcimPay verify if a card's already on file — Helcim refuses - // to re-save it, breaking retries after a partial-failed signup. - const [, existing] = await Promise.all([ - getOrCreateCustomer(), - $fetch('/api/helcim/existing-card').catch((err) => { + // Fast-path: when both Helcim ids are already cached on the member doc + // AND a card's on file, we can skip the paid getOrCreateCustomer round + // trip entirely and go straight to subscription creation. + const hasCachedHelcimIds = Boolean( + memberData.value?.helcimCustomerId && memberData.value?.helcimCustomerCode + ) + + let existing = null + let probedExistingCard = false + let cardToken = null + + if (hasCachedHelcimIds) { + existing = await $fetch('/api/helcim/existing-card').catch((err) => { console.warn('[payment] existing-card lookup failed, falling back to verify flow:', err) return null - }), - ]) + }) + probedExistingCard = true + if (existing?.cardToken) { + customerId.value = memberData.value.helcimCustomerId + customerCode.value = memberData.value.helcimCustomerCode + cardToken = existing.cardToken + } + } - let cardToken = existing?.cardToken || null + if (!cardToken) { + // Skip HelcimPay verify if a card's already on file — Helcim refuses + // to re-save it, breaking retries after a partial-failed signup. + const [, existingFromFull] = await Promise.all([ + getOrCreateCustomer(), + probedExistingCard + ? Promise.resolve(existing) + : $fetch('/api/helcim/existing-card').catch((err) => { + console.warn('[payment] existing-card lookup failed, falling back to verify flow:', err) + return null + }), + ]) + + cardToken = existingFromFull?.cardToken || null + } if (!cardToken) { await initializeHelcimPay( diff --git a/app/pages/events/index.vue b/app/pages/events/index.vue index 3621574..c001e7c 100644 --- a/app/pages/events/index.vue +++ b/app/pages/events/index.vue @@ -133,9 +133,8 @@ const filterOptions = [ const { data: eventsData } = await useFetch("/api/events"); const { data: seriesData } = await useFetch("/api/series"); -const now = new Date(); - const filteredEvents = computed(() => { + const now = new Date(); if (!eventsData.value) return []; return eventsData.value.filter((event) => { if (!includePastEvents.value && new Date(event.startDate) < now) diff --git a/app/pages/index.vue b/app/pages/index.vue index d813d33..de89b01 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -131,12 +131,10 @@ const DEFAULT_WIKI_FEATURE_TITLE = "What is a cooperative studio?"; const { data: wikiFeature } = await useFetch( "/api/site-content/homepage.wiki_feature", - { default: () => ({ title: "", body: "" }) } + { default: () => ({ title: "", body: "" }) }, ); -const hasCustomWikiFeature = computed( - () => !!wikiFeature.value?.body?.trim() -); +const hasCustomWikiFeature = computed(() => !!wikiFeature.value?.body?.trim()); const customWikiParagraphs = computed(() => { const body = wikiFeature.value?.body?.trim() || ""; @@ -166,7 +164,7 @@ const circleData = [ label: "Practitioner", metaphor: "The alcove", blurb: - "Where experience is shared and knowledge given back. You're here to teach, advise, mentor, and help shape the program itself. Alumni welcome.", + "Where experience is shared and knowledge given back. You're here to support newcomers, help shape the Cooperative Foundations program, and find peers.", }, ]; diff --git a/app/pages/join.vue b/app/pages/join.vue index 32cc816..1a88b43 100644 --- a/app/pages/join.vue +++ b/app/pages/join.vue @@ -64,26 +64,37 @@

Pay what you can

- Baby Ghosts Studio Development Fund is a registered Canadian charity. - Members who file Canadian taxes can claim their contributions. - We'll help you set up tax receipts once you've joined. + Baby Ghosts Studio Development Fund is a registered Canadian + charity. Members who file Canadian taxes can claim their + contributions. We'll help you set up tax receipts once you've + joined.

Pay what you can. If you can pay more, you're making room for @@ -118,7 +129,7 @@ type="text" placeholder="Your name" required - > + />

@@ -129,7 +140,7 @@ type="email" placeholder="you@example.com" required - > + />
@@ -141,7 +152,7 @@ type="radio" name="circle" value="community" - > + />
-
+
-

{{ guidanceLabel }}

+

+ {{ guidanceLabel }} +

- You'll be charged ${{ firstCharge }} today (${{ form.contributionAmount }}/month × 12). + You'll be charged ${{ firstCharge }} today + (${{ form.contributionAmount }}/month × 12).

- Then ${{ firstCharge }} every {{ cadence === 'annual' ? 'year' : 'month' }}, until you cancel. + Then + ${{ firstCharge }} every + {{ cadence === "annual" ? "year" : "month" }}, until you cancel.

-