Commit graph

181 commits

Author SHA1 Message Date
0f2f1d1cbf chore(visual): Phase 4 audit polish on event/series surface
Migrates event/series UI from Tailwind/Nuxt UI form components to the
zine pattern (dashed borders, CSS-var palette, native inputs). Restructures
single-event and series detail/index pages to the two-column grid pattern
matching about.vue and member/dashboard.vue.

Touches:
- app/components/EventSeriesTicketCard.vue — phantom-palette → CSS-var
  migration (--candle, --ember, --surface), color="gray" → "neutral"
- app/components/EventTicketCard.vue — --candle-faint border var
- app/components/EventTicketPurchase.vue — accent-color: var(--candle)
- app/pages/events/[slug].vue — page-fill flex chain, .event-body grid
- app/pages/events/index.vue — series link uses series.id (was _id)
- app/pages/series/[id].vue — two-column layout (1fr/280px) + sidebar
- app/pages/series/index.vue — full rewrite to dashed-border zine pattern

Per docs/specs/events-visual-audit-findings.md Phase 4. Behavior unchanged.
2026-04-25 18:41:04 +01:00
e227f29bcd feat(events): block self-cancel of paid registrations, add refunds policy
Self-cancel endpoint now rejects paid registrations (public, series_pass,
or paid member tickets) with a 403 pointing to /policies/refunds. Free
and $0-member registrations still self-cancel as before. Adds the
refunds policy page referenced in the error message.
2026-04-20 19:34:04 +01:00
0ce61756b7 feat(emails): warmer copy across invite, welcome, and event emails
Friendlier tone + ghost emoji on invite/welcome subjects; invite
templates now offer a reply-to-this-email fallback; tighten OIDC
wiki sign-in and event registration confirmation copy.
2026-04-20 13:48:38 +01:00
9c9dc49628 feat(join): add charity / tax-receipt factual note near contribution tiers
Adds a small paragraph directly below the tier list stating the
Baby Ghosts Studio Development Fund charity status, noting that
Canadian taxpayers can claim contributions, and that setup for
receipts happens after joining. Styled in parallel to
.solidarity-note (12px, --text-dim, 1.65 line-height) so it reads as
a bullet, not a banner.

Scope is /join only — /accept-invite and /member/account copy is
untouched per spec §3.
2026-04-20 13:23:49 +01:00
335a4db7cc fix(account): show payment history + next-charge for paid-then-$0 members
Three related changes on /member/account:

1. Payment History section now renders when contributionAmount > 0 OR
   past payments exist. Previously a paid member who switched to $0 lost
   visibility of their own past charges.

2. New "Next charge: $X on DATE" row renders above the transaction list
   when nextPaymentDate is available, using --candle dashed border.

3. server/api/helcim/subscription.get.js now reads dateBilling from
   Helcim's GET response and handles data as either object or array.
   Helcim's real shape is {data: {id, dateBilling, ...}} — the old code
   expected {data: [{nextBillingDate}]} and returned empty strings, so
   the Membership-card "Next payment" row never rendered for members
   whose cached date was missing. subscription.post.js and
   update-contribution.post.js have the same wrong field name in their
   CREATE flows; left for a follow-up — the GET refresh masks it.

Manual edit-flow and admin-flow tests also recorded in
docs/LAUNCH_READINESS.md.
2026-04-20 12:36:18 +01:00
a80728f0a8 feat(signup): unify cadence UX across accept-invite, join, and account
Extract shared SignupFlowOverlay component. Static "Monthly Contribution"
label on all three contribution inputs (was misleadingly dynamic).
"Per Year"/"Per Month" toggle copy; Per Year default on accept-invite,
Per Month default on join. Live billing-summary card on both signup
flows. Welcome-heading on dashboard via ?welcome=1 for new signups.
$0-member polish on account page (hide payment-history + Solidarity
Fund prompts). State-aware contribution-change hint. Invite accept now
creates Helcim customer and sets auth cookie server-side for both free
and paid branches. Pre-registrant invite + /join signup flows manually
verified against Cleo Nguyen preReg and $0-$50 variants.
2026-04-20 12:34:59 +01:00
7704557f16 merge: catch up with feature/helcim-plan-consolidation base
# Conflicts:
#	server/api/auth/member.get.js
#	server/api/members/update-contribution.post.js
#	tests/server/api/update-contribution.test.js
2026-04-19 21:33:40 +01:00
dfc03f851b fix(review): accept arbitrary amounts in payment-setup; rename m.tier → m.amount in activity text 2026-04-19 19:15:32 +01:00
b17e006d65 feat(frontend): rename contributionTier → contributionAmount across remaining pages 2026-04-19 19:08:57 +01:00
5ef0cc845f feat(account): replace tier control with amount input + guidance chips 2026-04-19 19:03:08 +01:00
4d10c4e0a2 feat(join): replace tier dropdown with amount input + guidance chips 2026-04-19 18:59:24 +01:00
5d6fcdd78d 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).
2026-04-19 18:32:04 +01:00
03eee45cbd refactor(contributions): tighten requiresPayment contract; use findLast 2026-04-19 18:10:57 +01:00
62c606b30a feat(contributions): rewrite client config as preset-based helpers 2026-04-19 18:07:43 +01:00
e7ad076d6a fix(a11y): raise circle description contrast to WCAG AA
The .circle-desc text used --text-faint, which failed WCAG AA on the
selected/hover tile surfaces (4.01:1 light / 4.31:1 dark). Promote to
--text-dim to clear 4.5:1 against all tile states.
2026-04-19 17:23:19 +01:00
19c77a3ab6 feat(account): in-app payment history + change card
Add Payment history section (live-read from Helcim, with loading/empty/error states)
and Change card flow (HelcimPay.js zero-dollar auth -> POST /api/helcim/update-card)
to /member/account. Relabel Helcim portal link to "Advanced billing in Helcim →"
and demote it to a secondary link at the bottom of the billing group.
2026-04-19 16:36:19 +01:00
f2e2cedb67 fix(join): redirect immediately on subscription success
Removes the 3-second setTimeout that deferred navigateTo('/welcome').
The overlay success state was a holdover from the pre-refactor Step-3
inline block; now that /welcome is the single welcome surface, the
delay just stalls a completed action and fights the continuous-flow
goal of the overlay.
2026-04-19 12:58:07 +01:00
968a127f96 fix(join): move flow overlay outside v-if so it survives auth flip
After createSubscription() calls checkMemberStatus(), isAuthenticated
flips to true and the <template v-else> branch unmounts, taking the
Teleport (and its overlay) with it. The authenticated 'You're already a
member' UI then showed for the 3-second pre-redirect delay, producing a
visible flash before navigateTo('/welcome') fired.

Teleport now lives at the root div alongside the v-if/v-else branches,
so the overlay stays mounted through the auth state transition and
covers the page continuously until the redirect.
2026-04-19 12:53:50 +01:00
f7c6bd88e7 refactor(join): move success state into overlay; remove step 2/3 UI 2026-04-19 12:23:19 +01:00
1bb59f07be refactor(join): auto-open Helcim modal after form submit 2026-04-19 12:21:10 +01:00
5a4c09f988 feat(join): add flow overlay scaffolding for submit→redirect states 2026-04-19 12:19:01 +01:00
a22a576bff fix(join): raise signup form above supporting copy 2026-04-19 12:16:30 +01:00
fd9ce5bc2c fix(ui): disambiguate annual tier labels
"$50/yr" was ambiguous — could mean the $5 tier in annual mode or the
$50 tier in monthly mode. On /join the dropdown now shows both prices
("$5/mo → $50/yr") in annual mode. On the account page TierPicker
gains a subtitle slot; annual mode shows "$N/mo tier" beneath the
annual price so members recognize which tier they're on.
2026-04-18 22:06:38 +01:00
fb337a4277 feat(account): display cadence and annual pricing in tier selector 2026-04-18 18:08:10 +01:00
748a84d001 chore(join): drop unused contributionOptions + formatTierAmount label param 2026-04-18 18:04:54 +01:00
673f881b54 refactor(join): use getTierAmount helper for cadence pricing 2026-04-18 18:02:04 +01:00
cd0d3f7167 feat(join): cadence selector with annual pricing (monthly×10)
Radio-pair cadence selector (Monthly / Annual) added to the join form,
reusing the existing .circle-radio styling. contributionItems computed
reactively; all tier labels and the left-column price list update on
toggle. cadence submitted with the subscription payload. payment-setup
hardcoded to monthly (annual upgrades go through /join).
2026-04-18 17:59:10 +01:00
be0e6e7699 refactor(config): cadence-keyed plan id, add getTierAmount, drop per-tier helcimPlanId 2026-04-18 17:19:05 +01:00
c816d4b373 style(payment-setup): drop ColumnsLayout wrapper 2026-04-18 17:06:34 +01:00
37a58cb0eb feat(member): pending_payment retains access, soften status copy
pending_payment now grants the same RSVP/peer-support capabilities as active,
and status banner/label copy is rewritten to be non-threatening ("Setting up
payment", "Paused", "Closed"). Aligns member-facing copy across the account
page with the capability model.
2026-04-18 17:06:22 +01:00
15329e3e84 refactor(events): gate member benefits on hasMemberAccess
Extracts hasMemberAccess(member) in tickets.js and uses it across event
registration, ticket purchase, and series purchase flows so guest, suspended,
and cancelled records no longer count as members while pending_payment still
does.
2026-04-18 17:06:17 +01:00
c5e901ed24 feat(signup): community guidelines agreement and policies routes
Introduces /community-guidelines and /policies/{privacy,terms,[slug]} pages,
swaps the signup/invite checkbox from agreedToTerms to agreedToGuidelines,
adds Member.agreement.acceptedAt, and stamps the field when a Helcim
customer is created.
2026-04-18 17:06:10 +01:00
e0d11e47f4 chore: remove admin series-management stub actions 2026-04-17 17:27:27 +01:00
6f9e6a3d98 feat(events): guest accounts for public event registration
Non-members who register for an event now get a persistent identity:
with consent, a status:"guest" Member is upserted and an auth cookie is
set so the "You're Registered" state survives a page refresh.

Tiered auto-login matches passwordless-auth norms — auto-login is only
safe when the account holds no privileges:
- New email → create guest + cookie
- Returning guest → cookie
- Existing non-guest (active/pending/etc.) → attach ticket only, no
  cookie, confirmation email includes a sign-in link

Guests are gated on status === "guest", so admin/middleware code that
keys on status === "active" naturally excludes them. Guests are also
treated as non-members for ticket pricing/validation to prevent picking
up member-only pricing on their second registration.
2026-04-16 21:23:31 +01:00
7e7672d52b New SiteContent. 2026-04-16 21:11:14 +01:00
02222a5c16 Copy and layout improvements. 2026-04-16 21:11:05 +01:00
1e9e9c4d97 fix(auth): stop wiki login loop to coming-soon and surface non-member state
Some checks failed
Test / vitest (push) Failing after 6m9s
Test / playwright (push) Has been skipped
Test / visual (push) Has been skipped
Test / Notify on failure (push) Successful in 2s
Members (and pre-registrants) hitting wiki.ghostguild.org were getting bounced
to /coming-soon with a "Pre-Register" link, even when the OIDC flow was
working correctly.

- Allowlist /auth/oidc-error, /auth/logout-confirm, /auth/logout-success,
  and /verify in the coming-soon middleware so OIDC errors and main-site
  magic links stop redirecting to the pre-register page.
- Raise OIDC Interaction TTL from 10m to 15m so it outlives the magic-link
  JWT and legitimate members don't hit expired-interaction errors when they
  click the email a few minutes late.
- Differentiate the "email isn't a registered member" response on the wiki
  login route and show a dedicated "Not a member yet" state with a
  pre-register link and contact email, instead of the misleading
  "Check your inbox" that silently failed.
2026-04-15 17:55:55 +01:00
2394248d53 Updates
Some checks failed
Test / vitest (push) Failing after 6m9s
Test / visual (push) Has been skipped
Test / playwright (push) Has been skipped
Test / Notify on failure (push) Successful in 2s
2026-04-15 17:45:09 +01:00
28040f44f4 refactor(board): atomic delete + query limit + composable cleanup
Some checks failed
Test / vitest (push) Failing after 7m17s
Test / playwright (push) Has been skipped
Test / visual (push) Has been skipped
Test / Notify on failure (push) Successful in 1s
Delete uses findOneAndDelete with author match (no TOCTOU window);
existence check only runs on miss to distinguish 403 vs 404. Posts
list capped at 200. Drop unused resolveTagChannel and refreshParams;
route slack URL building through the composable's slackUrl helper.
2026-04-15 12:47:53 +01:00
f691f095dc feat(board): inline delete confirmation + a11y polish
Some checks failed
Test / vitest (push) Failing after 6m2s
Test / playwright (push) Has been skipped
Test / visual (push) Has been skipped
Test / Notify on failure (push) Successful in 2s
Replace window.confirm with an inline Delete? / Cancel / Confirm flow on
post cards. Add focus-visible outlines, initials in avatar placeholders,
and promote post/form titles from h3 to h2 for heading order.
2026-04-14 22:15:50 +01:00
7292b11c0b 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
2026-04-14 20:35:37 +01:00
9a560f2a3b feat(board): redesign classifieds + Slack channel creation
Adds AdminGhost bot token for admin-only Slack channel creation, refreshes
BoardPostCard/Form layouts, and expands admin board-channels management.
2026-04-14 20:20:17 +01:00
6f3d088763 fix(board): surface delete errors via toast 2026-04-14 17:38:32 +01:00
f3df1945bd chore(board): remove old board tests, update seed + onboarding tests 2026-04-14 17:31:46 +01:00
7707068f36 feat(board): admin page for managing board channels 2026-04-14 17:27:06 +01:00
4b3ba411dd fix(board): unwrap API envelope in composables, isolate member profile fetch 2026-04-14 17:24:30 +01:00
f8bc5502ba feat(board): replace board/peer support with posts list on member profile 2026-04-14 17:22:04 +01:00
698f786951 refactor(board): accept refresh params in useBoardPosts mutators 2026-04-14 17:19:48 +01:00
61d33f5db3 feat(board): replace profile board section with posts list 2026-04-14 17:17:09 +01:00
5bdc3244bd fix(board): handle submit errors + tolerate tag fetch failure 2026-04-14 17:14:22 +01:00