Skip to content

feat: returning members on Overview and team join requests (#401, #411) - #462

Merged
elliotBraem merged 13 commits into
mainfrom
feat/returning-members-team-requests-401-411
Oct 28, 2026
Merged

elliotBraem merged 13 commits into
mainfrom
feat/returning-members-team-requests-401-411

Conversation

@adenarc

@adenarc adenarc commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #459. The base is feat/member-landing-396-393-403, and I'll retarget it to main once #459 merges.

Closes #401, closes #411. Two commits, #401 first.

#401 Returning members on Overview

  • Organizers, owners and admins see the share of people who checked in at 2+ distinct events of the community, out of everyone who checked in at least once, all time. The stat shows the share and "x of y people".
  • Read per event, as Show returning members next to join counts #401 describes: listDiscoveryActivities for every node of the community, then listOnboardingCodes({ eventId }), then getOnboardingStatus. Upstream reads run at most 6 at a time.
  • Deduplicated by event ID. Codes with no event ID, or for events outside the community, aren't counted. People who left still count.
  • If any event returns 20 codes, the stat is labelled May be incomplete. It shows No eligible check-ins yet when there are codes but no check-ins, and is hidden when there are no codes. A failed read shows a retry, never 0.
  • The scope line says "All time, from the check-ins on record" plus the read time. Cached per organization with a 30s stale time, the same as the join counts.
  • Uses the existing canOrganize, so platform admins get no organizer bypass.

#411 Members ask to join a team

  • New api/ tables: team_join_requests and team_join_endorsements (migration 0006). At most one pending request per team per member, enforced by a partial unique index.
  • New procedures: getTeamJoinBoard, requestToJoinTeam, withdrawTeamJoinRequest, endorseTeamJoinRequest, decideTeamJoinRequest. The caller always comes from the session. Every call reads members, teams and the caller's own teams upstream in the caller's own context.
  • Only teams with a recognized Feature Area are listed, so Event Teams are left out. Only current team members can endorse, once each, and endorsing doesn't change the status. Only owners and admins can approve or decline.
  • Approval calls upstream addTeamMember in the approver's context, and the request is marked approved only if that call succeeds. If it fails, the request stays pending and the error shows.
  • Pending requests close with requester_left, team_deleted or team_not_joinable when that becomes true. They keep the team name to display after the team is gone.
  • Names come from upstream member names and never fall back to an email.
  • Overview gets a Teams section covering the member, team-member and owner views, plus loading, error and empty states. Copy is in en/es/fr/zh. No auth code was added here.

Tested

  • pnpm run typecheck, pnpm run lint and pnpm run test all pass: api 411, ui 1050, plugins 28 + 6.
  • New tests:
    • Unit tests for the returning-members calculation and the team-join policy.
    • Query integration tests: per-event listing over more than 20 codes, an event with exactly 20 codes, partial failure, community switching.
    • API integration tests for each procedure by role (plain member, team member, admin, owner, platform admin, outsider): duplicates, withdraw, failed upstream add, requester left, team deleted, team lost its area.
    • Component tests for the stat and the Teams section.
  • Browser (Brave, local stack, seeded community with 2 past events, 3 codes, 5 check-ins and 3 teams). Seed data was removed afterwards.
    • Owner: 25% (1 of 4), and Teams shows Operations and Community with the event team left out. Approved a seeded request, and the member was added to the team upstream.
    • Plain member (also a platform admin): no stat. Asked to join, which showed "Request sent", then withdrew.
    • Organizer through the Events team: stat shows. The team-member view shows "You're on this team" and Endorse only, and endorsing changed the count without changing the status.
    • No console errors.
  • Not tested:
    • Exact 375px in the browser: Brave's narrowest window is 596px. There was no horizontal scroll there, and the rows use flex-wrap.
    • The "may be incomplete" label in the browser (covered by component tests).
    • Production data.

Known gaps

  • If an admin approves at the same moment the requester withdraws, the person is added to the team but the request is recorded as withdrawn.
  • A pending request isn't closed when someone adds the requester to the team by hand from the org page. Approving it is harmless because the upstream add is idempotent.

Returning members as owner

Teams as owner with a pending request

Plain member after asking to join

Organizer and team member after endorsing

Teams at narrow width

Extract Explore's updates and channels into a shared component and use it on
the public page. Keep the bulletin members-only and say so in its editor hint.
Home and My community now pick a community's default node with one helper:
first by name, then by ID.
Home leads plain members with Open {community}, then Stake NEAR; owners,
organizers and admins keep their steps. Remove the broken Add your email
prompts and the add-email dialog. Overview gains a membership intro, Social
Updates and channels from the public listing, and the header links to the
public page.
Add one personal-organization check and use it in the sidebar, switcher,
Home, /orgs and My community. Personal-only users get Start a community;
a member with one community and nothing active has it opened; a personal
active organization next to real memberships asks them to choose one.
…member (#403)

The approval regression now expects an approved sole community to open on
its own instead of needing Make active.
@multi-review

multi-review Bot commented Oct 28, 2026

Copy link
Copy Markdown

Multi Review · 26df3b7

This PR adds two things. The first is a client-side "returning members" stat, built by reading activities, onboarding codes and onboarding status per event. The second is a team join-request workflow: two new tables, five procedures, and a Teams section on Overview. Authorization comes from the caller's session and upstream membership. Requests go through pending-only conditional updates and a partial unique index, and an approval is recorded only after the upstream add succeeds. Overall risk is low. The one issue I found is that closed requests stay on the board forever.

1 issue(s)

What was checked (11 areas)
  • TeamJoinRequestsLive list/get/create/settle/close/endorse and their callers in api/src/index.ts
  • loadTeamCommunity: runs on every board read and every mutation (getTeamJoinBoard, requestToJoinTeam, and the other three through loadTeamRequest); closures happen as a side effect of reads
  • listAllMembers pagination, which feeds the requester_left closure
  • canSeeTeamRequest / presentTeamRequest visibility and canEndorse/canDecide rules by role
  • decideTeamJoinRequest: addTeamMember failure path, settle race (the PR already lists this gap), CONNECTION_ERROR mapping
  • requestToJoinTeam: unique-violation handling, already-on-team check, non-joinable team check
  • migration 0006 vs the schema.ts indexes and enums
  • readReturningMembers / mapLimited concurrency and failure propagation; eligibleCodes/summarizeReturningMembers dedup and the incomplete flag
  • ReturningMembers component states and the canOrganize gate; nodes in the route context come from listNodes for the community tenant
  • TeamRequests component: mutation invalidation, the isMine split, own settled requests
  • i18n keys used by the new components exist in all four locales

Findings

  • 🟡 Low api/src/index.ts:683 Closed team requests stay on the reviewer/team-member board forever and hide the empty state
    canSeeTeamRequest shows other people's requests when they are pending or closed, and teamJoinRequests.list loads every closed row in the organization on every call (inArray(status, ["pending","closed"])). Approved, declined and withdrawn requests drop off the board once settled. Closed ones (requester_left, team_deleted, team_not_joinable) never expire. Example: a member asks to join Operations and then leaves the community. From then on, every owner, admin and Operations member sees that request under "Requests" with no actions, on every load. Because the list is no longer empty, the "No team requests waiting." message never shows again. These rows build up over time, and every board read and every mutation fetches all of them through loadTeamCommunity.
    Suggested fix: Treat closed like the other settled states for non-requesters: show only pending to reviewers and team members. Also, in list, only include closed rows when the viewer is the requester. If closed requests should stay visible, limit them to a recent window.

Comment @multi-review to re-review new commits, or @multi-review full for a full review.

@elliotBraem
elliotBraem added this pull request to stack #465 October 28, 2026 16:35
* feat: show members the events they checked in at (#400)

Organizers' Overview syncs each community event's onboarding codes and
check-ins from auth into a City Node record. Members see their events on
Overview for that community and on Home across communities, with gaps
labelled as incomplete.

* fix: tighten attendance coverage and batch its queries (#400)

* refactor(api): read a community's name and events in one query (#400)

* fix(api): re-list only unsettled events and surface failed sweeps (#400)

Settled past events are skipped unless one of their codes changed, a
failed read of the latest codes marks the community incomplete, and
event rows use the community's current name.
…96-393-403

# Conflicts:
#	ui/src/i18n/messages/common.ts
… into feat/returning-members-team-requests-401-411

# Conflicts:
#	api/src/contract.ts
#	api/src/db/migrations/meta/0006_snapshot.json
#	api/src/db/migrations/meta/_journal.json
#	api/src/db/schema.ts
#	api/src/index.ts
#	ui/src/routes/_authenticated/_dashboard/dashboard/node/-node-overview.test.tsx
#	ui/src/routes/_authenticated/_dashboard/dashboard/node/index.tsx
Base automatically changed from feat/member-landing-396-393-403 to main October 28, 2026 17:35
@elliotBraem

Copy link
Copy Markdown
Contributor

I'm resolving @somewherelostt @adenarc

…s-team-requests-401-411

# Conflicts:
#	api/src/contract.ts
#	api/src/db/migrations/meta/_journal.json
#	api/src/db/schema.ts
#	api/src/index.ts
#	ui/src/routes/_authenticated/_dashboard/dashboard/node/-node-overview.test.tsx
#	ui/src/routes/_authenticated/_dashboard/dashboard/node/index.tsx
@elliotBraem
elliotBraem merged commit 4ba4c87 into main Oct 28, 2026
6 checks passed
@elliotBraem
elliotBraem deleted the feat/returning-members-team-requests-401-411 branch October 28, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Members ask to join a team Show returning members next to join counts

2 participants