Skip to content

fix(web): fetch real group members from contract using groupId#223

Open
mvanhorn wants to merge 1 commit into
BlockHaven-Labs:mainfrom
mvanhorn:osc/49-group-members-real-data
Open

fix(web): fetch real group members from contract using groupId#223
mvanhorn wants to merge 1 commit into
BlockHaven-Labs:mainfrom
mvanhorn:osc/49-group-members-real-data

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

Summary

GroupMembers accepted a groupId prop but never read it; it rendered a hardcoded array of 8 fake members at the top of apps/web/components/group-members.tsx. Real members from the savings contract were never displayed. This PR replaces the hardcoded array with a contract fetch via useSavingsContract().getMembersByGroup(groupId), mapped onto the existing render surface.

Closes #49

Why this matters

The component is wired into a group detail page that already passes the right groupId, so users opening a group's "Members" tab were seeing eight invented Stellar addresses regardless of which group they were looking at. The contract already exposes get_members(groupId) through useSavingsContract; the hook just wasn't being called. This is exactly the scenario the issue describes as the bug, and the fix is to wire the existing hook to the existing prop.

Approach

The component is now a client component ("use client" is required because it uses useEffect, useState, and the wallet/contract hooks). The fetch runs inside a useEffect keyed on [canFetch, groupId, getMembersByGroup, publicKey]. Updates happen in try/catch/finally callbacks of an async IIFE so the effect body itself does not synchronously call setState (react-hooks/set-state-in-effect is enforced and was the only new lint error in the first iteration).

Contract members map to the existing render shape with three small adapters. Addresses are truncated to XXXX...XXXX for display, the position number comes from joinOrder, and joinTimestamp (a u64 of unix seconds, BigInt) is formatted as Mmm YYYY via toLocaleDateString. Contract status maps to the existing badge variants: Active -> pending, PaidCurrentRound -> paid, Overdue -> overdue, Defaulted -> overdue (closest at-risk match), ReceivedPayout -> received. The isYou flag is derived by comparing each member's full address against publicKey from useWallet(). MemberStatusBadge itself is unchanged per the issue notes.

The component renders a skeleton row stack while fetching (matches dashboard-stats.tsx), an empty-state message when no members come back, and an inline error message if the contract call rejects.

Acceptance criteria (issue body)

  • Hardcoded const members = [...] array removed
  • "use client" added
  • useSavingsContract().getMembersByGroup(groupId) called inside a useEffect
  • Contract statuses mapped to existing badge variants (no new variants)
  • isYou derived from publicKey === member.address
  • Loading state added
  • Empty state added
  • joinTimestamp (u64) formatted as a readable date string

Verification

npx tsc --noEmit is clean for the changed file. npx eslint components/group-members.tsx is clean; the previously reported react-hooks/set-state-in-effect violation is gone after moving updates into the async callback. Pre-existing lint problems elsewhere in the repo (for example dashboard-stats.tsx and useUserGroups.ts) are untouched.

Notes

Contract MemberStatus.Defaulted has no direct existing badge variant; it is mapped to overdue because both communicate an at-risk state. Happy to add a dedicated defaulted variant in a follow-up if preferred.

GroupMembers accepted a `groupId` prop but never used it; it rendered a
hardcoded array of 8 fake members. Real members from the savings contract
were never displayed.

Changes:
- Calls `useSavingsContract().getMembersByGroup(groupId)` inside an effect.
- Maps the contract Member shape (address, joinOrder, joinTimestamp,
  status, totalContributed, hasReceivedPayout) to the existing render
  surface. join_timestamp (u64 unix seconds, BigInt) is formatted as a
  'Mmm YYYY' string.
- Maps contract MemberStatus to the existing badge variants:
    Active -> pending
    PaidCurrentRound -> paid
    Overdue -> overdue
    Defaulted -> overdue
    ReceivedPayout -> received
- Determines 'isYou' by comparing each member address to publicKey from
  useWallet().
- Adds a skeleton loading state while fetching.
- Adds an empty state when no members are returned, and an error state
  when the contract call fails.
- Adds 'use client' (the component is now stateful and uses hooks).

MemberStatusBadge variants are unchanged per the issue notes.

Closes BlockHaven-Labs#49
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

@mvanhorn is attempting to deploy a commit to the aminubabafatima8-gmailcom's projects Team on Vercel.

A member of the Team first needs to authorize it.

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.

Bug: GroupMembers Component Ignores groupId and Shows Fake Members

1 participant