Commit graph

109 commits

Author SHA1 Message Date
4f9c11a755 feat(billing): add payment history API route
Add GET /api/helcim/payment-history returning the authenticated
member's normalized Helcim card transactions (newest first, capped
at 50). Resolves helcimCustomerId -> customerCode via getHelcimCustomer
before calling listHelcimCustomerTransactions. Returns
{ transactions: [] } when the member has no helcimCustomerId, and
{ transactions: [], error: 'unavailable' } (HTTP 200) on Helcim
failures so the UI can render fallback copy.

Covered by unit tests at tests/server/api/helcim-payment-history.test.js
(auth, missing customer id, happy path, both Helcim failure paths,
missing customerCode).
2026-04-19 16:26:19 +01:00
0ca38e5588 fix(auth): expose helcimCustomerId on /api/auth/member
The member account page gates the Helcim customer portal link on
`memberData.helcimCustomerId`, but this endpoint (the source for
`useAuth().memberData`) omitted the field, so the link hid for every
member regardless of Helcim enrollment. Add the field to the response.
2026-04-19 13:04:46 +01:00
fb337a4277 feat(account): display cadence and annual pricing in tier selector 2026-04-18 18:08:10 +01:00
0eeed94772 feat(contribution): free-to-paid uses cadence plan id, persists billingCadence 2026-04-18 17:37:35 +01:00
e8c81cf062 feat(contribution): paid-to-paid tier swap via recurringAmount PATCH 2026-04-18 17:32:22 +01:00
4b5ea9bbd8 fix(helcim): restore subscriptionStartDate on paid-tier activation 2026-04-18 17:28:05 +01:00
8d43804c7f feat(helcim): create subscription by cadence with recurringAmount
Replace tier-based plan lookup with cadence-keyed lookup, compute
recurringAmount via getTierAmount, persist billingCadence on member.
Delete both manual-fallback blocks; Helcim failure now surfaces as 500.
2026-04-18 17:25:14 +01:00
4f567e9586 refactor(helcim): wrapped PATCH body, first-activation welcome email guard
Moves updateHelcimSubscription to the live-verified wrapped shape
(PATCH /subscriptions { subscriptions: [{ id, ...payload }] }), adds a prior-
status check so sendWelcomeEmail only fires on pending_payment to active
transitions, short-circuits get-or-create-customer when a valid
helcimCustomerId is already on file, and replaces member.save() Slack-status
writes with findByIdAndUpdate({ runValidators: false }) to avoid save-time
validator pitfalls.
2026-04-18 17:06:30 +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
2c834da40a chore: remove placeholder payment block from members/create 2026-04-17 17:16:32 +01:00
3ba633cce2 chore: remove dead guest-register event route
The /api/events/[id]/guest-register endpoint has no production
callers: it's superseded by tickets/purchase.post.js, which
handles guest Member upsert via status:"guest" when
body.createAccount is true. Drops the route file, its
source-assertion tests, guestRegisterSchema, and its validation
coverage.
2026-04-17 16:36:34 +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
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
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
1fc937a26a refactor(board): delete old board routes, absorb slackHandle into profile PATCH
- Delete server/api/members/me/board.patch.js and server/api/board/suggestions.get.js
- Add boardSlackHandle to memberProfileUpdateSchema; remove boardPrivacy
- profile.patch.js: write boardSlackHandle -> board.slackHandle; drop boardPrivacy
- Remove privacy.board field from Member model
- onboarding/status.get.js: hasProfileTags now requires only craftTags; hasEngagedBoard uses BoardPost.exists
- onboarding/track.post.js: graduation check uses BoardPost.exists instead of board.topics elemMatch
- members/[id].get.js and directory.get.js: reduce board response to slackHandle only; drop connectionTag and peerSupport filters
2026-04-14 16:29:45 +01:00
6a440a846d feat: board post + channel API routes
Implements Phase 2a of board classifieds redesign:

- GET/POST /api/board/posts (list with tag/author filters, create)
- PATCH/DELETE /api/board/posts/:id (author-only)
- GET /api/board/channels (member)
- POST /api/admin/board-channels (admin)
- PATCH/DELETE /api/admin/board-channels/:id (admin)

Adds board_post_created activity type.
2026-04-14 16:25:42 +01:00
a0f60bcdc0 fix: rename hasEngagedEcology → hasEngagedBoard in onboarding status, clean up stale ecology references 2026-04-14 12:25:24 +01:00
091ec58073 rename communityEcology → board across backend
Model, schemas, API routes, activity log, and all server handlers
updated. Old ecology/ and community-ecology routes removed, new
board/ routes added. Tests updated and new board-suggestions tests
written (10 cases).
2026-04-14 12:00:15 +01:00
59d6e97787 Member/Ecology revamp.
Some checks failed
Test / vitest (push) Failing after 7m23s
Test / playwright (push) Has been skipped
Test / visual (push) Has been skipped
Test / Notify on failure (push) Successful in 2s
2026-04-14 09:25:09 +01:00
c6b970a621 Design token updates.
Some checks failed
Test / vitest (push) Successful in 10m47s
Test / playwright (push) Failing after 9m11s
Test / visual (push) Failing after 9m11s
Test / Notify on failure (push) Successful in 2s
2026-04-11 23:24:38 +01:00
50a358b294 feat(wiki): add batch tag remove mode to admin wiki page
Add add/remove toggle to batch tag picker. Clean up unused requireAdmin
import from wiki sync route.
2026-04-09 23:52:00 +01:00
a516f172fb refactor: extract escapeRegex and validateTagSlugs server utils
Deduplicate tag validation and regex escaping into shared auto-imported
utils. Add tag validation to wiki patch/batch-tag routes. Remove
duplicate tags field from event schema.
2026-04-09 23:51:56 +01:00
20c961113d Merge branch 'worktree-agent-ac00ecc9' 2026-04-09 22:38:36 +01:00
8b2f6d5240 Merge branch 'worktree-agent-abf17134' 2026-04-09 22:38:36 +01:00
e4f2efd6d0 feat(wiki): add admin wiki management API routes 2026-04-09 22:36:44 +01:00
d3a5c1a3a7 feat(wiki): add tag-based wiki recommendations API 2026-04-09 22:36:19 +01:00
4a475ca5ba Merge branch 'worktree-agent-a54bb856'
# Conflicts:
#	server/models/wikiArticle.js
2026-04-09 22:34:09 +01:00
bda0fe6eb7 Merge branch 'worktree-agent-acfdfab5'
# Conflicts:
#	server/models/event.js
2026-04-09 22:33:54 +01:00
abca0fb7d6 Merge branch 'worktree-agent-a5a0c7d9' 2026-04-09 22:33:41 +01:00
b93c735442 Merge branch 'worktree-agent-a53b58a7' 2026-04-09 22:33:41 +01:00
905b5155e2 feat(wiki): add Outline utility and wiki sync API 2026-04-09 22:33:06 +01:00
2166ee32ca feat(events): add tag validation to admin event create/edit routes 2026-04-09 22:32:32 +01:00
fcbad24f3e feat(events): add tag-based event recommendations API 2026-04-09 22:32:11 +01:00
56376d1995 feat(onboarding): add onboarding status and track API routes with tests 2026-04-09 22:31:57 +01:00
3144cbe213 feat(onboarding): redirect /welcome to /member/dashboard 2026-04-09 22:28:57 +01:00
0b3896d984 refactor(community): rename Community Connections → Community Ecology
Some checks failed
Test / vitest (push) Successful in 11m42s
Test / playwright (push) Failing after 9m27s
Test / visual (push) Failing after 9m53s
Test / Notify on failure (push) Successful in 2s
Simplify the feature to pure discovery (filter by topic, see matching
members, copy Slack handle). Drop the connection request/confirm flow
entirely — Connection model, 7 API endpoints, useConnections composable,
and TagInput component deleted.

- Rename communityConnections → communityEcology in schema, API, pages
- Delete legacy fields: offering, lookingFor, peerSupport
- New /ecology page, /api/ecology/suggestions, community-ecology.patch
- Nav: "Connections" → "Ecology", remove pending-count badge
- Fix auth/member.get.js missing craftTags + communityEcology
- Add community_ecology_updated activity log type
- Expose slackHandle conditionally when offerPeerSupport is true
- Add migration script at scripts/migrate-to-ecology.js (run before deploy)
2026-04-09 09:07:15 +01:00
9577929e0d refactor(peer-support): delete provably dead code (Phase 1)
The Skills Exchange + Peer Support feature was replaced by Community
Connections on 2026-04-05, but several files and code paths were left
in place as backward-compat. None are reachable from the live UI:

- usePeerSupport.js composable: not imported anywhere
- PeerSupportBadge.vue: not imported anywhere
- peer-support.vue: stub redirect with no incoming links
- /api/peer-support.get.js: only consumed by usePeerSupport
- /api/members/me/peer-support.patch.js: same
- profile.patch.js offering/lookingFor write branches: profile form
  no longer sends these fields (only writes communityConnections.*)
- PEER_SUPPORT_ENABLED/DISABLED activity types and renderers: only
  written by the deleted peer-support.patch endpoint. The activityText
  formatter has a fallback for unknown types so existing records
  still display ("peer support enabled" with a generic icon).

Tests updated to drop peerSupportUpdateSchema coverage and the
offering/lookingFor passthrough assertion.

schemas.js cleanup deferred — concurrent communityConnections →
communityEcology rename is in flight in the working tree.
2026-04-08 22:28:35 +01:00
130e5bfa9f refactor(helcim): use helper in unused admin endpoints 2026-04-08 22:11:25 +01:00
0d792c7c70 refactor(members): use helcim helper + fix wrong card-lookup URL 2026-04-08 22:03:42 +01:00
03d6a66b84 refactor(helcim): use helper in subscription endpoint 2026-04-08 21:53:26 +01:00
7b4b6feb51 refactor(helcim): use centralized helper in 5 simple endpoints 2026-04-08 21:44:18 +01:00
92e7dae74c feat(admin): add restore dismissed alerts flow
Some checks failed
Test / vitest (push) Successful in 11m48s
Test / playwright (push) Failing after 9m50s
Test / visual (push) Failing after 9m19s
Test / Notify on failure (push) Successful in 2s
Admins can now surface dismissed alert types without waiting for the
underlying data to change. Adds a collapsible "Restore dismissed"
section below the active alerts with per-type checkboxes.

- ALERT_METADATA map in adminAlerts.js as the single source of truth
  for slug → title/severity; detectors refactored to reference it
- GET /api/admin/alerts/dismissed returns this admin's dismissals
  joined with metadata (title, severity, dismissedAt)
- POST /api/admin/alerts/restore deletes dismissals by alertType[],
  returns the deleted count
- AdminAlertsPanel fetches both active + dismissed; stays visible
  when either is non-empty; checkboxes + "Restore selected" button
- adminAlertRestoreSchema validates the POST body against the enum
- Auth guards test covers both new routes
2026-04-08 12:22:35 +01:00
21cf8d79b3 feat(admin): add POST /api/admin/alerts/dismiss endpoint 2026-04-08 11:20:10 +01:00
f0284c60b4 feat(admin): add GET /api/admin/alerts endpoint 2026-04-08 11:17:50 +01:00
fb25e72215 Huge bunch of UI/UX improvements and tweaks!
Some checks failed
Test / vitest (push) Successful in 10m36s
Test / playwright (push) Failing after 9m23s
Test / visual (push) Failing after 9m13s
Test / Notify on failure (push) Successful in 2s
2026-04-06 16:17:12 +01:00